Skip to content

Commit

Permalink
scene: Render background dynamically
Browse files Browse the repository at this point in the history
Previously, the scene background was rendered into a pixmap. This had two drawbacks:
  - It uses a lot of memory (and this gets worse quickly when the scene is large).
  - The pre-rendered pixmap was rasterized. Zooming in made the grid appear pixelated.

Fixes #59
  • Loading branch information
Tectu committed May 10, 2024
1 parent 40897d4 commit f58e8cb
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 94 deletions.
2 changes: 2 additions & 0 deletions qschematic/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ function(setup_target_common target)
wire_system/wire.hpp
wire_system/point.hpp
wire_system/net.hpp
background.hpp
netlist.hpp
netlist_writer_json.hpp
netlistgenerator.hpp
Expand Down Expand Up @@ -102,6 +103,7 @@ function(setup_target_common target)
wire_system/wire.cpp
wire_system/point.cpp
wire_system/net.cpp
background.cpp
scene.cpp
settings.cpp
utils.cpp
Expand Down
83 changes: 83 additions & 0 deletions qschematic/background.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#include "background.hpp"

#include <QPen>
#include <QBrush>
#include <QPainter>
#include <QStyleOptionGraphicsItem>

using namespace QSchematic;

Background::Background(QGraphicsItem* parent) :
QGraphicsRectItem(parent)
{
setFlag(QGraphicsItem::ItemUsesExtendedStyleOption, true); // For QStyleOptionGraphicsItem::exposedRect
}

void
Background::setSettings(const Settings& settings)
{
m_settings = settings;
}

void
Background::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
// ToDo: Use QStyleOptionGraphicsItem::exposedRect

// Background pen
QPen backgroundPen;
backgroundPen.setStyle(Qt::NoPen);

// Background brush
QBrush backgroundBrush;
backgroundBrush.setStyle(Qt::SolidPattern);
backgroundBrush.setColor(Qt::white);

// Grid pen
QPen gridPen;
gridPen.setStyle(Qt::SolidLine);
gridPen.setColor(Qt::gray);
gridPen.setCapStyle(Qt::RoundCap);
gridPen.setWidth(m_settings.gridPointSize);

// Grid brush
QBrush gridBrush;
gridBrush.setStyle(Qt::NoBrush);

// Get the rects
//
// sr = scene rect
// ep = exposed rect
const QRectF sr = rect();
const QRectF er = (option ? option->exposedRect : rect());

painter->save();
painter->setRenderHint(QPainter::Antialiasing, m_settings.antialiasing);

// Draw background
painter->setPen(backgroundPen);
painter->setBrush(backgroundBrush);
painter->drawRect(rect());

// Draw the grid if supposed to
if (m_settings.showGrid && (m_settings.gridSize > 0)) {
qreal left = int(rect().left()) - (int(rect().left()) % m_settings.gridSize);
qreal top = int(rect().top()) - (int(rect().top()) % m_settings.gridSize);

painter->setPen(gridPen);
painter->setBrush(gridBrush);
for (qreal x = left; x < sr.right(); x += m_settings.gridSize) {
for (qreal y = top; y < sr.bottom(); y += m_settings.gridSize)
painter->drawPoint(x, y);
}
}

// Mark the origin if supposed to
if (m_settings.debug) {
painter->setPen(Qt::NoPen);
painter->setBrush(QBrush(Qt::red));
painter->drawEllipse(-6, -6, 12, 12);
}

painter->restore();
}
29 changes: 29 additions & 0 deletions qschematic/background.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#pragma once

#include "settings.hpp"

#include <QGraphicsRectItem>

namespace QSchematic
{

class Background :
public QGraphicsRectItem
{
public:
explicit
Background(QGraphicsItem* parent = nullptr);

~Background() override = default;

void
setSettings(const Settings& settings);

void
paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;

private:
Settings m_settings;
};

}
105 changes: 24 additions & 81 deletions qschematic/scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <QTimer>

#include "scene.hpp"
#include "background.hpp"
#include "commands/item_move.hpp"
#include "commands/item_add.hpp"
#include "commands/item_remove.hpp"
Expand Down Expand Up @@ -54,13 +55,16 @@ Scene::Scene(QObject* parent) :
_popup->setPos(_lastMousePos + QPointF{ 5, 5 });
});

// Stuff
connect(this, &QGraphicsScene::sceneRectChanged, [this]{
renderCachedBackground();
// Background
setupBackground();
connect(this, &QGraphicsScene::sceneRectChanged, [this](const QRectF rect){
if (_background) {
// Adjust scene rect (make it smaller) because otherwise the scene resizes automatically to a larger size to accomodate
// the background rect and we end up in an infinite loop.
// ToDo
//_background->setRect(rect.adjusted(10, 10, -10, -10));
}
});

// Prepare the background
renderCachedBackground();
}

Scene::~Scene()
Expand Down Expand Up @@ -170,6 +174,10 @@ Scene::from_container(const gpds::container& container)
void
Scene::setSettings(const Settings& settings)
{
// Update background
if (_background)
_background->setSettings(settings);

// Update settings of all items
for (auto& item : items())
item->setSettings(settings);
Expand All @@ -181,7 +189,6 @@ Scene::setSettings(const Settings& settings)
_settings = settings;

// Redraw
renderCachedBackground();
update();
}

Expand Down Expand Up @@ -273,8 +280,11 @@ Scene::clear()
// Now that all the top-level items are safeguarded we can call the underlying scene's clear()
QGraphicsScene::clear();

// NO longer dirty
// No longer dirty
clearIsDirty();

// Setup the background again
setupBackground();
}

bool
Expand Down Expand Up @@ -933,14 +943,6 @@ Scene::dropEvent(QGraphicsSceneDragDropEvent* event)
}
}

void
Scene::drawBackground(QPainter* painter, const QRectF& rect)
{
const QPointF& pixmapTopleft = rect.topLeft() - sceneRect().topLeft();

painter->drawPixmap(rect, _backgroundPixmap, QRectF(pixmapTopleft.x(), pixmapTopleft.y(), rect.width(), rect.height()));
}

QVector2D
Scene::itemsMoveSnap(const std::shared_ptr<Items::Item>& items, const QVector2D& moveBy) const
{
Expand All @@ -949,73 +951,14 @@ Scene::itemsMoveSnap(const std::shared_ptr<Items::Item>& items, const QVector2D&
return moveBy;
}

QPixmap
Scene::renderBackground(const QRect& rect) const
{
// Create pixmap
QPixmap pixmap(rect.width(), rect.height());

// Grid pen
QPen gridPen;
gridPen.setStyle(Qt::SolidLine);
gridPen.setColor(Qt::gray);
gridPen.setCapStyle(Qt::RoundCap);
gridPen.setWidth(_settings.gridPointSize);

// Grid brush
QBrush gridBrush;
gridBrush.setStyle(Qt::NoBrush);

// Create a painter
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing, _settings.antialiasing);

// Draw background
pixmap.fill(Qt::white);

// Draw the grid if supposed to
if (_settings.showGrid && (_settings.gridSize > 0)) {
qreal left = int(rect.left()) - (int(rect.left()) % _settings.gridSize);
qreal top = int(rect.top()) - (int(rect.top()) % _settings.gridSize);

// Create a list of points
QVector<QPointF> points;
for (qreal x = left; x < rect.right(); x += _settings.gridSize) {
for (qreal y = top; y < rect.bottom(); y += _settings.gridSize)
points.append(QPointF(x,y));
}

// Draw the actual grid points
painter.setPen(gridPen);
painter.setBrush(gridBrush);
painter.drawPoints(points.data(), points.size());
}

// Mark the origin if supposed to
if (_settings.debug) {
painter.setPen(Qt::NoPen);
painter.setBrush(QBrush(Qt::red));
painter.drawEllipse(-6, -6, 12, 12);
}

painter.end();

return pixmap;
}

void
Scene::renderCachedBackground()
Scene::setupBackground()
{
// Create the pixmap
const QRect& rect = sceneRect().toRect();
if (rect.isNull() || !rect.isValid())
return;

// Render background
_backgroundPixmap = std::move(renderBackground(rect));

// Update
update();
_background = new Background(nullptr);
_background->setRect(sceneRect());
_background->setZValue(z_value_background);
_background->setSettings(_settings);
QGraphicsScene::addItem(_background);
}

void
Expand Down
19 changes: 6 additions & 13 deletions qschematic/scene.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ namespace QSchematic
class WireNet;
}

class Background;

/**
* The QSchematic Scene.
*
Expand All @@ -41,6 +43,8 @@ namespace QSchematic
public:
static constexpr const char* gpds_name = "qschematic";

qreal z_value_background = -10'000;

enum Mode {
NormalMode,
WireMode,
Expand Down Expand Up @@ -210,7 +214,6 @@ namespace QSchematic
void dragMoveEvent(QGraphicsSceneDragDropEvent* event) override;
void dragLeaveEvent(QGraphicsSceneDragDropEvent* event) override;
void dropEvent(QGraphicsSceneDragDropEvent* event) override;
void drawBackground(QPainter* painter, const QRectF& rect) override;

/**
* This gets called just before the item is actually being moved by moveBy. Subclasses may
Expand All @@ -221,21 +224,11 @@ namespace QSchematic
QVector2D
itemsMoveSnap(const std::shared_ptr<Items::Item>& item, const QVector2D& moveBy) const;

/**
* Renders the background.
*
* @param rect The scene rectangle. This is guaranteed to be non-null & valid.
*/
[[nodiscard]]
virtual
QPixmap
renderBackground(const QRect& rect) const;

private Q_SLOTS:
void wirePointMoved(wire& rawWire, int index);

private:
void renderCachedBackground();
void setupBackground();
void setupNewItem(Items::Item& item);
void updateNodeConnections(const Items::Node* node);
void generateConnections();
Expand Down Expand Up @@ -272,7 +265,6 @@ namespace QSchematic
// ItemUtils::ItemsCustodian<Item> _items;
// ItemUtils::ItemsCustodian<WireNet> m_nets;

QPixmap _backgroundPixmap;
std::function<std::shared_ptr<Items::Wire>()> _wireFactory;
int _mode = NormalMode;
std::shared_ptr<Items::Wire> _newWire;
Expand All @@ -287,6 +279,7 @@ namespace QSchematic
std::shared_ptr<Items::Item> _highlightedItem = nullptr;
QTimer* _popupTimer = nullptr;
std::shared_ptr<QGraphicsProxyWidget> _popup;
Background* _background = nullptr;
};

}

0 comments on commit f58e8cb

Please sign in to comment.