wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
ICRenderer.cpp
Go to the documentation of this file.
1// Copyright 2015 - 2026, GIBIS-UNIFESP and the wiRedPanda contributors
2// SPDX-License-Identifier: GPL-3.0-or-later
3
5
6#include <cmath>
7
8#include <QGraphicsScene>
9#include <QPainter>
10#include <QScopeGuard>
11#include <QStyleOptionGraphicsItem>
12#include <QSvgRenderer>
13
14#include "App/Element/IC.h"
17
20static QSvgRenderer &icLogoRenderer()
21{
22 static QSvgRenderer renderer(QStringLiteral(":/Components/Logic/ic-panda2.svg"));
23 return renderer;
24}
25
27{
28 // The body is now drawn as vectors in drawBody()/paint(); m_pixmap is kept only so that the
29 // base pixmapCenter()/boundingRect() have the right size (its image content is never displayed).
30 // It must encompass both the 64×64 body and any ports that extend beyond it.
31 const QSizeF boundsSize = ic.renderBodyBounds().size();
32
33 // Defense-in-depth: a non-finite boundary port position makes this size NaN, and
34 // QSizeF::toSize() asserts (!qIsNaN) on a NaN dimension → process abort. The load-side
35 // guards reject non-finite element position/rotation up front; skip regenerating the
36 // sizing pixmap here for any other geometry source rather than crash.
37 if (!std::isfinite(boundsSize.width()) || !std::isfinite(boundsSize.height())) {
38 return;
39 }
40
41 QPixmap sizingPixmap(boundsSize.toSize());
42 sizingPixmap.fill(Qt::transparent);
43 ic.m_appearance.setRenderPixmap(sizingPixmap);
44 ic.update();
45}
46
47void ICRenderer::drawBody(IC &ic, QPainter *painter)
48{
49 painter->save();
50 painter->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing, true);
51 // boundingRect()'s top-left may be negative when ports extend past the 64×64 body; align the
52 // local origin with it so the body lands exactly where the old rasterised pixmap was blitted.
53 painter->translate(ic.boundingRect().topLeft());
54 // The body footprint is the (correctly-sized) m_pixmap rect — exactly the area the old raster
55 // occupied — so the geometry is reproduced 1:1 at any zoom.
56 const QRectF bounds(ic.pixmap().rect());
57
58 const QColor bodyColor = ic.isEmbedded() ? QColor(90, 126, 160) : QColor(126, 126, 126);
59 const QColor outlineColor = ic.isEmbedded() ? QColor(58, 82, 110) : QColor(78, 78, 78);
60
61 // IC body: styled like a physical DIP package. 7px inset on each side (14px total) so the port
62 // connector dots visually overlap the border, matching the TruthTable and physical DIP look.
63 painter->setBrush(bodyColor);
64 painter->setPen(QPen(QBrush(outlineColor), 0.5, Qt::SolidLine));
65 const QRectF finalRect(QPointF(7, 0), QSizeF(bounds.width() - 14, bounds.height()));
66 painter->drawRoundedRect(finalRect, 3, 3);
67
68 // Centre the wiRedPanda mascot logo on the body, rendered as vectors at its native size.
69 // The mascot is decoration, like the baked-in SVG pin text: counter-orient it about its own
70 // centre (rotate outer, flip inner — the inverse of the item's Flip∘Rotate) so it reads
71 // upright at any element orientation.
72 QSvgRenderer &logo = icLogoRenderer();
73 const QSizeF logoSize = logo.defaultSize();
74 const QRectF logoRect(finalRect.center() - QPointF(logoSize.width() / 2, logoSize.height() / 2), logoSize);
75 painter->save();
76 painter->translate(logoRect.center());
77 painter->rotate(-ic.rotation());
78 painter->scale(ic.isFlippedX() ? -1 : 1, ic.isFlippedY() ? -1 : 1);
79 painter->translate(-logoRect.center());
80 logo.render(painter, logoRect);
81 painter->restore();
82
83 // Thin dark strip at the bottom edge to simulate the package shadow/bevel.
84 painter->setBrush(outlineColor);
85 painter->setPen(QPen(QBrush(outlineColor), 0.5, Qt::SolidLine));
86 QRectF shadowRect(finalRect.bottomLeft(), finalRect.bottomRight());
87 shadowRect.adjust(0, -3, 0, 0);
88 painter->drawRoundedRect(shadowRect, 3, 3);
89
90 // Orientation notch (semicircle) at the top centre, matching the physical DIP pin-1 convention.
91 // drawChord angle parameters are in 1/16th-degree units; -180*16 = lower half-circle.
92 const QRectF topCenter(finalRect.topLeft() + QPointF(18, -12), QSizeF(24, 24));
93 painter->drawChord(topCenter, 0, -180 * 16);
94
95 painter->restore();
96}
97
98void ICRenderer::generatePreviewPixmap(IC &ic, const QList<QGraphicsItem *> &items)
99{
100 // Split the freshly-deserialized items into elements and connections. The
101 // boundary Input/Output elements are still in their designed form here; the
102 // substitution to proxy Nodes happens later in processLoadedItems().
103 QVector<GraphicElement *> elements;
104 QVector<Connection *> connections;
105 elements.reserve(items.size());
106 connections.reserve(items.size());
107 for (auto *item : items) {
108 if (auto *conn = qgraphicsitem_cast<Connection *>(item)) {
109 connections.append(conn);
110 } else if (auto *elm = qgraphicsitem_cast<GraphicElement *>(item)) {
111 elements.append(elm);
112 }
113 }
114
115 // Skip for empty or very large circuits.
116 if (elements.isEmpty() || elements.size() > ICPreviewPopup::MaxElementCount) {
117 ic.m_previewPixmap = QPixmap();
118 return;
119 }
120
121 // Temporarily borrow the items into a throwaway scene so QGraphicsScene::render()
122 // can be used without disturbing the real scene. The scope guard guarantees
123 // cleanup even if render() throws.
124 QGraphicsScene tempScene;
125 tempScene.setBackgroundBrush(QColor(42, 42, 42));
126
127 auto cleanup = qScopeGuard([&] {
128 for (auto *elm : std::as_const(elements)) { tempScene.removeItem(elm); }
129 for (auto *conn : std::as_const(connections)) { tempScene.removeItem(conn); }
130 });
131
132 for (auto *elm : std::as_const(elements)) {
133 tempScene.addItem(elm);
134 }
135 for (auto *conn : std::as_const(connections)) {
136 tempScene.addItem(conn);
137 }
138
139 // Compute the bounding rect with some padding
140 const QRectF sourceRect = tempScene.itemsBoundingRect().adjusted(-16, -16, 16, 16);
141
142 // Defense-in-depth: a non-finite item geometry makes the bounding rect NaN,
143 // and QSizeF::toSize() asserts (!qIsNaN) on a NaN dimension → process abort.
144 // The load-side guards reject non-finite element position/rotation up front;
145 // skip the (non-essential) preview here for any other geometry source rather
146 // than crash. Surfaced by libFuzzer (fuzz_ic_file).
147 if (!std::isfinite(sourceRect.width()) || !std::isfinite(sourceRect.height())) {
148 ic.m_previewPixmap = QPixmap();
149 return;
150 }
151
152 // Scale to fit within max preview dimensions while preserving aspect ratio
153 QSize targetSize = sourceRect.size().toSize();
154 targetSize.scale(ICPreviewPopup::MaxWidth, ICPreviewPopup::MaxHeight, Qt::KeepAspectRatio);
155
156 if (targetSize.isEmpty()) {
157 ic.m_previewPixmap = QPixmap();
158 return;
159 }
160
161 // QPixmap(QSize) is uninitialised; tempScene.render() paints the background
162 // brush over the source→target affine, but subpixel rounding can leave a
163 // 1-pixel sliver unpainted at the right/bottom edge, exposing whatever was
164 // in memory (commonly white on Windows). Fill explicitly to avoid that.
165 QPixmap preview(targetSize);
166 preview.fill(QColor(42, 42, 42));
167
168 QPainter painter(&preview);
169 painter.setRenderHint(QPainter::Antialiasing);
170 tempScene.render(&painter, QRectF(), sourceRect);
171 painter.end();
172
173 ic.m_previewPixmap = preview;
174}
Connection: a wire that connects an output port to an input port in the circuit scene.
Frameless popup widget that shows a preview of an IC's internal circuit.
static QSvgRenderer & icLogoRenderer()
ICRenderer: draws an IC's body and builds its cached pixmaps.
Integrated Circuit (IC) graphic element that encapsulates a sub-circuit file.
void setRenderPixmap(const QPixmap &pixmap)
ElementAppearance m_appearance
qreal rotation() const
Returns the current rotation angle of this element in degrees.
bool isFlippedY() const
Returns true if this element is mirrored along the Y axis (vertical flip).
QPixmap pixmap() const
Returns the pixmap currently displayed by this element.
QRectF renderBodyBounds() const
Footprint of a "procedural render body" (IC/Mux/Demux/TruthTable): the nominal 64x64 body unioned wit...
bool isFlippedX() const
Returns true if this element is mirrored along the X axis (horizontal flip).
static constexpr int MaxWidth
Maximum preview dimensions in pixels.
static constexpr int MaxHeight
static constexpr int MaxElementCount
static void generatePixmap(IC &ic)
static void drawBody(IC &ic, QPainter *painter)
static void generatePreviewPixmap(IC &ic, const QList< QGraphicsItem * > &items)
Graphic element representing an Integrated Circuit (sub-circuit) box.
Definition IC.h:31
bool isEmbedded() const override
Returns true if this element is an embedded IC (not file-backed). Base returns false.
Definition IC.h:81
void save(QDataStream &stream, SerializationOptions options) const override
Definition IC.cpp:83
QRectF boundingRect() const override
Definition IC.cpp:322