diff --git a/src/ProjectPlayer.qml b/src/ProjectPlayer.qml index 766cefa..68149c1 100644 --- a/src/ProjectPlayer.qml +++ b/src/ProjectPlayer.qml @@ -16,6 +16,7 @@ ProjectScene { property alias cloneLimit: loader.cloneLimit property alias spriteFencing: loader.spriteFencing property alias mute: loader.mute + property alias hqPen: projectPenLayer.hqPen property bool showLoadingProgress: true readonly property bool loading: priv.loading readonly property int downloadedAssets: loader.downloadedAssets @@ -141,9 +142,9 @@ ProjectScene { engine: loader.engine anchors.top: parent.top anchors.left: parent.left - width: stageWidth - height: stageHeight - scale: stageScale + width: hqPen ? parent.width : stageWidth + height: hqPen ? parent.height : stageHeight + scale: hqPen ? 1 : stageScale transformOrigin: Item.TopLeft visible: !priv.loading } diff --git a/src/penlayer.cpp b/src/penlayer.cpp index 628bec1..031d504 100644 --- a/src/penlayer.cpp +++ b/src/penlayer.cpp @@ -58,11 +58,7 @@ void PenLayer::setEngine(libscratchcpp::IEngine *newEngine) if (m_engine && QOpenGLContext::currentContext()) { m_projectPenLayers[m_engine] = this; - QOpenGLFramebufferObjectFormat fboFormat; - fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); - m_fbo = std::make_unique(m_engine->stageWidth(), m_engine->stageHeight(), fboFormat); - Q_ASSERT(m_fbo->isValid()); - m_texture = Texture(m_fbo->texture(), m_fbo->size()); + createFbo(); if (!m_painter) m_painter = std::make_unique(); @@ -106,6 +102,21 @@ void PenLayer::setEngine(libscratchcpp::IEngine *newEngine) emit engineChanged(); } +bool PenLayer::hqPen() const +{ + return m_hqPen; +} + +void PenLayer::setHqPen(bool newHqPen) +{ + if (m_hqPen == newHqPen) + return; + + m_hqPen = newHqPen; + createFbo(); + emit hqPenChanged(); +} + void scratchcpprender::PenLayer::clear() { if (!m_fbo) @@ -138,16 +149,23 @@ void scratchcpprender::PenLayer::drawLine(const PenAttributes &penAttributes, do m_painter->beginFrame(m_fbo->width(), m_fbo->height()); + // Apply scale (HQ pen) + x0 *= m_scale; + y0 *= m_scale; + x1 *= m_scale; + y1 *= m_scale; + // Translate to Scratch coordinate system - double stageWidthHalf = m_engine->stageWidth() / 2; - double stageHeightHalf = m_engine->stageHeight() / 2; + double stageWidthHalf = width() / 2; + double stageHeightHalf = height() / 2; x0 += stageWidthHalf; y0 = stageHeightHalf - y0; x1 += stageWidthHalf; y1 = stageHeightHalf - y1; // Set pen attributes - m_painter->setLineWidth(penAttributes.diameter); + const double diameter = penAttributes.diameter * m_scale; + m_painter->setLineWidth(diameter); m_painter->setStrokeStyle(penAttributes.color); m_painter->setFillStyle(penAttributes.color); m_painter->setLineJoin(QNanoPainter::JOIN_ROUND); @@ -156,11 +174,11 @@ void scratchcpprender::PenLayer::drawLine(const PenAttributes &penAttributes, do m_painter->beginPath(); // Width 1 and 3 lines need to be offset by 0.5 - const double offset = (std::fmod(std::max(4 - penAttributes.diameter, 0.0), 2)) / 2; + const double offset = (std::fmod(std::max(4 - diameter, 0.0), 2)) / 2; // If the start and end coordinates are the same, draw a point, otherwise draw a line if (x0 == x1 && y0 == y1) { - m_painter->circle(x0 + offset, y0 + offset, penAttributes.diameter / 2); + m_painter->circle(x0 + offset, y0 + offset, diameter / 2); m_painter->fill(); } else { m_painter->moveTo(x0 + offset, y0 + offset); @@ -223,6 +241,9 @@ void PenLayer::stamp(IRenderedTarget *target) } else costume = target->stageModel()->stage()->currentCostume(); + // Apply scale (HQ pen) + scale *= m_scale; + const double bitmapRes = costume->bitmapResolution(); const double centerX = costume->rotationCenterX() / bitmapRes; const double centerY = costume->rotationCenterY() / bitmapRes; @@ -234,8 +255,11 @@ void PenLayer::stamp(IRenderedTarget *target) const double textureScale = texture.width() / static_cast(target->costumeWidth()); + // Apply scale (HQ pen) + x *= m_scale; + y *= m_scale; + // Translate the coordinates - // TODO: Apply scale (HQ pen) x = std::floor(x + m_texture.width() / 2.0); y = std::floor(-y + m_texture.height() / 2.0); @@ -352,8 +376,11 @@ QRgb PenLayer::colorAtScratchPoint(double x, double y) const const double width = m_texture.width(); const double height = m_texture.height(); + // Apply scale (HQ pen) + x *= m_scale; + y *= m_scale; + // Translate the coordinates - // TODO: Apply scale x = std::floor(x + width / 2.0); y = std::floor(-y + height / 2.0); @@ -393,7 +420,6 @@ const libscratchcpp::Rect &PenLayer::getBounds() const } for (const QPointF &point : points) { - // TODO: Apply scale double x = point.x() - width / 2; double y = -point.y() + height / 2; @@ -410,10 +436,10 @@ const libscratchcpp::Rect &PenLayer::getBounds() const bottom = y; } - m_bounds.setLeft(left); - m_bounds.setTop(top); - m_bounds.setRight(right + 1); - m_bounds.setBottom(bottom - 1); + m_bounds.setLeft(left / m_scale); + m_bounds.setTop(top / m_scale); + m_bounds.setRight(right / m_scale + 1); + m_bounds.setBottom(bottom / m_scale - 1); } return m_bounds; @@ -439,6 +465,33 @@ QNanoQuickItemPainter *PenLayer::createItemPainter() const return new PenLayerPainter; } +void PenLayer::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + if (m_hqPen && newGeometry != oldGeometry) + createFbo(); + + QNanoQuickItem::geometryChange(newGeometry, oldGeometry); +} + +void PenLayer::createFbo() +{ + if (!QOpenGLContext::currentContext() || !m_engine) + return; + + QOpenGLFramebufferObjectFormat fboFormat; + fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); + + QOpenGLFramebufferObject *newFbo = new QOpenGLFramebufferObject(width(), height(), fboFormat); + Q_ASSERT(newFbo->isValid()); + + if (m_fbo) + QOpenGLFramebufferObject::blitFramebuffer(newFbo, m_fbo.get()); + + m_fbo.reset(newFbo); + m_texture = Texture(m_fbo->texture(), m_fbo->size()); + m_scale = width() / m_engine->stageWidth(); +} + void PenLayer::updateTexture() { if (!m_fbo) diff --git a/src/penlayer.h b/src/penlayer.h index a7d2969..2f93a9c 100644 --- a/src/penlayer.h +++ b/src/penlayer.h @@ -19,6 +19,7 @@ class PenLayer : public IPenLayer Q_OBJECT QML_ELEMENT Q_PROPERTY(libscratchcpp::IEngine *engine READ engine WRITE setEngine NOTIFY engineChanged) + Q_PROPERTY(bool hqPen READ hqPen WRITE setHqPen NOTIFY hqPenChanged) public: PenLayer(QNanoQuickItem *parent = nullptr); @@ -30,6 +31,9 @@ class PenLayer : public IPenLayer libscratchcpp::IEngine *engine() const override; void setEngine(libscratchcpp::IEngine *newEngine) override; + bool hqPen() const; + void setHqPen(bool newHqPen); + 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; @@ -45,17 +49,22 @@ class PenLayer : public IPenLayer signals: void engineChanged(); + void hqPenChanged(); protected: QNanoQuickItemPainter *createItemPainter() const override; + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; private: + void createFbo(); void updateTexture(); static std::unordered_map m_projectPenLayers; bool m_antialiasingEnabled = true; libscratchcpp::IEngine *m_engine = nullptr; + bool m_hqPen = false; std::unique_ptr m_fbo; + double m_scale = 1; std::unique_ptr m_painter; std::unique_ptr m_glF; Texture m_texture; diff --git a/test/lines_hq.png b/test/lines_hq.png new file mode 100644 index 0000000..c78d1d3 Binary files /dev/null and b/test/lines_hq.png differ diff --git a/test/penlayer/penlayer_test.cpp b/test/penlayer/penlayer_test.cpp index c7f62a6..653e8ed 100644 --- a/test/penlayer/penlayer_test.cpp +++ b/test/penlayer/penlayer_test.cpp @@ -56,13 +56,15 @@ TEST_F(PenLayerTest, Engine) ASSERT_EQ(penLayer.engine(), nullptr); EngineMock engine1, engine2; + penLayer.setWidth(480); + penLayer.setHeight(360); EXPECT_CALL(engine1, stageWidth()).WillOnce(Return(480)); - EXPECT_CALL(engine1, stageHeight()).WillOnce(Return(360)); penLayer.setEngine(&engine1); ASSERT_EQ(penLayer.engine(), &engine1); + penLayer.setWidth(500); + penLayer.setHeight(400); EXPECT_CALL(engine2, stageWidth()).WillOnce(Return(500)); - EXPECT_CALL(engine2, stageHeight()).WillOnce(Return(400)); penLayer.setEngine(&engine2); ASSERT_EQ(penLayer.engine(), &engine2); @@ -75,9 +77,10 @@ TEST_F(PenLayerTest, FramebufferObject) PenLayer penLayer; ASSERT_TRUE(penLayer.antialiasingEnabled()); - EngineMock engine1, engine2; + EngineMock engine1, engine2, engine3; + penLayer.setWidth(480); + penLayer.setHeight(360); EXPECT_CALL(engine1, stageWidth()).WillOnce(Return(480)); - EXPECT_CALL(engine1, stageHeight()).WillOnce(Return(360)); penLayer.setEngine(&engine1); QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); @@ -89,8 +92,9 @@ TEST_F(PenLayerTest, FramebufferObject) penLayer.setAntialiasingEnabled(false); ASSERT_FALSE(penLayer.antialiasingEnabled()); + penLayer.setWidth(500); + penLayer.setHeight(400); EXPECT_CALL(engine2, stageWidth()).WillOnce(Return(500)); - EXPECT_CALL(engine2, stageHeight()).WillOnce(Return(400)); penLayer.setEngine(&engine2); fbo = penLayer.framebufferObject(); @@ -98,11 +102,36 @@ TEST_F(PenLayerTest, FramebufferObject) ASSERT_EQ(fbo->height(), 400); ASSERT_EQ(fbo->format().attachment(), QOpenGLFramebufferObject::CombinedDepthStencil); ASSERT_EQ(fbo->format().samples(), 0); + + penLayer.setWidth(960); + penLayer.setHeight(720); + EXPECT_CALL(engine3, stageWidth()).WillOnce(Return(480)); + penLayer.setEngine(&engine3); + + fbo = penLayer.framebufferObject(); + ASSERT_EQ(fbo->width(), 960); + ASSERT_EQ(fbo->height(), 720); + ASSERT_EQ(fbo->format().attachment(), QOpenGLFramebufferObject::CombinedDepthStencil); + ASSERT_EQ(fbo->format().samples(), 0); + + EXPECT_CALL(engine3, stageWidth()).Times(3).WillRepeatedly(Return(100)); + penLayer.setHqPen(true); + penLayer.setWidth(500); + penLayer.setHeight(400); + penLayer.setEngine(&engine3); + + 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(), 0); } TEST_F(PenLayerTest, GetProjectPenLayer) { PenLayer penLayer; + penLayer.setWidth(480); + penLayer.setHeight(360); ASSERT_EQ(PenLayer::getProjectPenLayer(nullptr), nullptr); EngineMock engine1, engine2; @@ -110,13 +139,11 @@ TEST_F(PenLayerTest, GetProjectPenLayer) 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); @@ -134,8 +161,9 @@ TEST_F(PenLayerTest, Clear) { PenLayer penLayer; EngineMock engine; + penLayer.setWidth(480); + penLayer.setHeight(360); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); - EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); penLayer.setEngine(&engine); QOpenGLExtraFunctions glF(&m_context); @@ -175,99 +203,149 @@ TEST_F(PenLayerTest, DrawPoint) { PenLayer penLayer; penLayer.setAntialiasingEnabled(false); + penLayer.setWidth(480); + penLayer.setHeight(360); EngineMock engine; EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); - EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); penLayer.setEngine(&engine); - PenAttributes attr; - attr.color = QNanoColor(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 = QNanoColor(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 = QNanoColor(255, 50, 200, 185); - attr.diameter = 25.6; + auto draw = [&penLayer]() { + PenAttributes attr; + attr.color = QNanoColor(255, 0, 0); + attr.diameter = 3; + + penLayer.drawPoint(attr, 63, 164); + penLayer.drawPoint(attr, -56, 93); + penLayer.drawPoint(attr, 130, 77); + + attr.color = QNanoColor(0, 128, 0, 128); + attr.diameter = 10; + + penLayer.drawPoint(attr, 152, -158); + penLayer.drawPoint(attr, -228, 145); + penLayer.drawPoint(attr, -100, 139); + + attr.color = QNanoColor(255, 50, 200, 185); + attr.diameter = 25.6; + + penLayer.drawPoint(attr, -11, 179); + penLayer.drawPoint(attr, 90, -48); + penLayer.drawPoint(attr, -54, 21); + }; + + draw(); + + { + 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 HQ pen - resize existing texture 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); + penLayer.setHqPen(true); + penLayer.setWidth(720); + penLayer.setHeight(540); + + { + QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); + QImage image = fbo->toImage(); + QImage ref("points_resized.png"); + ASSERT_LE(fuzzyCompareImages(image, ref), 0.01); + } - 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 HQ pen - draw + penLayer.clear(); + draw(); + + { + QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); + QImage image = fbo->toImage(); + QBuffer buffer; + image.save(&buffer, "png"); + QFile ref("points_hq.png"); + ref.open(QFile::ReadOnly); + buffer.open(QFile::ReadOnly); + ASSERT_EQ(ref.readAll(), buffer.readAll()); + } } TEST_F(PenLayerTest, DrawLine) { PenLayer penLayer; penLayer.setAntialiasingEnabled(false); + penLayer.setWidth(480); + penLayer.setHeight(360); EngineMock engine; EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); - EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); penLayer.setEngine(&engine); - PenAttributes attr; - attr.color = QNanoColor(255, 0, 0); - attr.diameter = 3; + auto draw = [&penLayer]() { + PenAttributes attr; + attr.color = QNanoColor(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); + penLayer.drawLine(attr, 63, 164, -56, 93); + penLayer.drawLine(attr, 130, 77, 125, -22); - attr.color = QNanoColor(0, 128, 0, 128); - attr.diameter = 10; + attr.color = QNanoColor(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); + penLayer.drawLine(attr, 152, -158, -228, 145); + penLayer.drawLine(attr, -100, 139, 20, 72); - attr.color = QNanoColor(255, 50, 200, 185); - attr.diameter = 25.6; + attr.color = QNanoColor(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); + penLayer.drawLine(attr, -11, 179, 90, -48); + penLayer.drawLine(attr, -54, 21, 88, -6); + }; - QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); - QImage image = fbo->toImage().scaled(240, 180); - QBuffer buffer; - image.save(&buffer, "png"); - QFile ref("lines.png"); - ref.open(QFile::ReadOnly); - buffer.open(QFile::ReadOnly); - ASSERT_EQ(ref.readAll(), buffer.readAll()); + draw(); + + { + QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); + QImage image = fbo->toImage().scaled(240, 180); + QBuffer buffer; + image.save(&buffer, "png"); + QFile ref("lines.png"); + ref.open(QFile::ReadOnly); + buffer.open(QFile::ReadOnly); + ASSERT_EQ(ref.readAll(), buffer.readAll()); + } + + // Test HQ pen + penLayer.clear(); + EXPECT_CALL(engine, stageWidth()).Times(3).WillRepeatedly(Return(480)); + penLayer.setHqPen(true); + penLayer.setWidth(720); + penLayer.setHeight(540); + draw(); + + { + QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); + QImage image = fbo->toImage(); + QBuffer buffer; + image.save(&buffer, "png"); + QFile ref("lines_hq.png"); + ref.open(QFile::ReadOnly); + buffer.open(QFile::ReadOnly); + ASSERT_EQ(ref.readAll(), buffer.readAll()); + } } TEST_F(PenLayerTest, Stamp) { PenLayer penLayer; + penLayer.setWidth(480); + penLayer.setHeight(360); EngineMock engine; EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); - EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); penLayer.setEngine(&engine); ProjectLoader loader; @@ -302,19 +380,39 @@ TEST_F(PenLayerTest, Stamp) for (const auto &target : targets) penLayer.stamp(target.get()); - QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); - QImage image = fbo->toImage().scaled(240, 180); - QImage ref("stamp.png"); - ASSERT_LE(fuzzyCompareImages(image, ref), 0.1668); + { + QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); + QImage image = fbo->toImage().scaled(240, 180); + QImage ref("stamp.png"); + ASSERT_LE(fuzzyCompareImages(image, ref), 0.1668); + } + + // Test HQ pen + penLayer.clear(); + EXPECT_CALL(engine, stageWidth()).Times(3).WillRepeatedly(Return(480)); + penLayer.setHqPen(true); + penLayer.setWidth(720); + penLayer.setHeight(540); + + for (const auto &target : targets) + penLayer.stamp(target.get()); + + { + QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); + QImage image = fbo->toImage(); + QImage ref("stamp_hq.png"); + ASSERT_LE(fuzzyCompareImages(image, ref), 0.32); + } } TEST_F(PenLayerTest, TextureData) { PenLayer penLayer; + penLayer.setWidth(6); + penLayer.setHeight(4); penLayer.setAntialiasingEnabled(false); EngineMock engine; EXPECT_CALL(engine, stageWidth()).WillRepeatedly(Return(6)); - EXPECT_CALL(engine, stageHeight()).WillRepeatedly(Return(4)); penLayer.setEngine(&engine); PenAttributes attr; @@ -379,4 +477,74 @@ TEST_F(PenLayerTest, TextureData) ASSERT_EQ(bounds.top(), 1); ASSERT_EQ(bounds.right(), 2); ASSERT_EQ(bounds.bottom(), -2); + + // Test HQ pen + penLayer.clear(); + EXPECT_CALL(engine, stageWidth()).Times(3).WillRepeatedly(Return(480)); + penLayer.setHqPen(true); + penLayer.setWidth(720); + penLayer.setHeight(540); + + attr = PenAttributes(); + attr.color = QNanoColor(255, 0, 0); + attr.diameter = 1; + penLayer.drawLine(attr, -3, 2, 3, -2); + ASSERT_EQ(penLayer.colorAtScratchPoint(-3, 2), qRgb(255, 0, 0)); + ASSERT_EQ(penLayer.colorAtScratchPoint(0, 2), qRgba(0, 0, 0, 0)); + ASSERT_EQ(penLayer.colorAtScratchPoint(-1, 1), qRgb(255, 0, 0)); + + bounds = penLayer.getBounds(); + ASSERT_EQ(std::round(bounds.left() * 100) / 100, -3.33); + ASSERT_EQ(bounds.top(), 2); + ASSERT_EQ(std::round(bounds.right() * 100) / 100, 3.67); + ASSERT_EQ(bounds.bottom(), -3); + + attr.color = QNanoColor(0, 128, 0, 128); + attr.diameter = 2; + penLayer.drawLine(attr, -3, -2, 3, 2); + ASSERT_EQ(penLayer.colorAtScratchPoint(-3, 2), qRgb(255, 0, 0)); + ASSERT_EQ(penLayer.colorAtScratchPoint(0, 2), 0); + ASSERT_EQ(penLayer.colorAtScratchPoint(-1, 1), qRgb(255, 0, 0)); + + bounds = penLayer.getBounds(); + ASSERT_EQ(std::round(bounds.left() * 100) / 100, -3.33); + ASSERT_EQ(std::round(bounds.top() * 100) / 100, 2.67); + ASSERT_EQ(std::round(bounds.right() * 100) / 100, 4.33); + ASSERT_EQ(std::round(bounds.bottom() * 100) / 100, -3.67); + + penLayer.clear(); + ASSERT_EQ(penLayer.colorAtScratchPoint(-3, 2), 0); + ASSERT_EQ(penLayer.colorAtScratchPoint(0, 2), 0); + ASSERT_EQ(penLayer.colorAtScratchPoint(-1, 1), 0); + + bounds = penLayer.getBounds(); + ASSERT_EQ(bounds.left(), 0); + ASSERT_EQ(bounds.top(), 0); + ASSERT_EQ(bounds.right(), 0); + ASSERT_EQ(bounds.bottom(), 0); + + attr.color = QNanoColor(0, 255, 0, 255); + attr.diameter = 1; + penLayer.drawLine(attr, 0, -1, 1, 1); + ASSERT_EQ(penLayer.colorAtScratchPoint(0, 1), qRgba(0, 0, 0, 0)); + ASSERT_EQ(penLayer.colorAtScratchPoint(0, 0), qRgb(0, 255, 0)); + ASSERT_EQ(penLayer.colorAtScratchPoint(-3, 1), qRgba(0, 0, 0, 0)); + + bounds = penLayer.getBounds(); + ASSERT_EQ(bounds.left(), 0); + ASSERT_EQ(std::round(bounds.top() * 100) / 100, 1.33); + ASSERT_EQ(std::round(bounds.right() * 100) / 100, 1.67); + ASSERT_EQ(std::round(bounds.bottom() * 100) / 100, -1.67); + + attr.diameter = 2; + penLayer.drawPoint(attr, -2, 0); + ASSERT_EQ(penLayer.colorAtScratchPoint(0, 1), qRgba(0, 0, 0, 0)); + ASSERT_EQ(penLayer.colorAtScratchPoint(0, 0), qRgb(0, 255, 0)); + ASSERT_EQ(penLayer.colorAtScratchPoint(-3, 1), 0); + + bounds = penLayer.getBounds(); + ASSERT_EQ(std::round(bounds.left() * 100) / 100, -2.67); + ASSERT_EQ(std::round(bounds.top() * 100) / 100, 1.33); + ASSERT_EQ(std::round(bounds.right() * 100) / 100, 1.67); + ASSERT_EQ(std::round(bounds.bottom() * 100) / 100, -1.67); } diff --git a/test/points_hq.png b/test/points_hq.png new file mode 100644 index 0000000..4a6076c Binary files /dev/null and b/test/points_hq.png differ diff --git a/test/points_resized.png b/test/points_resized.png new file mode 100644 index 0000000..4770b99 Binary files /dev/null and b/test/points_resized.png differ diff --git a/test/stamp_hq.png b/test/stamp_hq.png new file mode 100644 index 0000000..b259b79 Binary files /dev/null and b/test/stamp_hq.png differ