1 /*
2 SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7 #include "painterrenderer.h"
8 #include "stackblur_p.h"
9 #include "view.h"
10 #include "render-logging.h"
11
12 #include <KOSMIndoorMap/SceneGraph>
13
14 #include <QDebug>
15 #include <QElapsedTimer>
16 #include <QFontMetricsF>
17 #include <QGuiApplication>
18 #include <QImage>
19 #include <QLinearGradient>
20 #include <QPainter>
21
22 using namespace KOSMIndoorMap;
23
24 PainterRenderer::PainterRenderer() = default;
25 PainterRenderer::~PainterRenderer() = default;
26
setPainter(QPainter * painter)27 void PainterRenderer::setPainter(QPainter *painter)
28 {
29 m_painter = painter;
30 }
31
render(const SceneGraph & sg,View * view)32 void PainterRenderer::render(const SceneGraph &sg, View *view)
33 {
34 QElapsedTimer frameTimer;
35 frameTimer.start();
36
37 m_view = view;
38 beginRender();
39 renderBackground(sg.backgroundColor());
40
41 for (const auto &layerOffsets : sg.layerOffsets()) {
42 const auto layerBegin = sg.itemsBegin(layerOffsets);
43 const auto layerEnd = sg.itemsEnd(layerOffsets);
44 //qDebug() << "rendering layer" << (*layerBegin)->layer;
45
46 // select elements currently in view
47 m_renderBatch.clear();
48 m_renderBatch.reserve(layerOffsets.second - layerOffsets.first);
49 const QRectF screenRect(QPointF(0, 0), QSizeF(m_view->screenWidth(), m_view->screenHeight()));
50 for (auto it = layerBegin; it != layerEnd; ++it) {
51 if ((*it).payload->inSceneSpace() && m_view->viewport().intersects((*it).payload->boundingRect())) {
52 m_renderBatch.push_back((*it).payload.get());
53 }
54 if ((*it).payload->inHUDSpace()) {
55 auto bbox = (*it).payload->boundingRect();
56 bbox.moveCenter(m_view->mapSceneToScreen(bbox.center()));
57 if (screenRect.intersects(bbox)) {
58 m_renderBatch.push_back((*it).payload.get());
59 }
60 }
61 }
62
63 for (auto phase : {SceneGraphItemPayload::FillPhase, SceneGraphItemPayload::CasingPhase, SceneGraphItemPayload::StrokePhase, SceneGraphItemPayload::LabelPhase}) {
64 beginPhase(phase);
65 for (const auto item : m_renderBatch) {
66 if ((item->renderPhases() & phase) == 0) {
67 continue;
68 }
69
70 if (auto i = dynamic_cast<PolygonItem*>(item)) {
71 renderPolygon(i, phase);
72 } else if (auto i = dynamic_cast<MultiPolygonItem*>(item)) {
73 renderMultiPolygon(i, phase);
74 } else if (auto i = dynamic_cast<PolylineItem*>(item)) {
75 renderPolyline(i, phase);
76 } else if (auto i = dynamic_cast<LabelItem*>(item)) {
77 renderLabel(i);
78 } else {
79 qCritical() << "Unsupported scene graph item!";
80 }
81 }
82 }
83 }
84
85 renderForeground(sg.backgroundColor());
86 endRender();
87 m_view = nullptr;
88
89 qCDebug(RenderLog) << "rendering took:" << frameTimer.elapsed() << "ms for" << sg.items().size() << "items on" << sg.layerOffsets().size() << "layers";
90 }
91
beginRender()92 void PainterRenderer::beginRender()
93 {
94 m_painter->save();
95 }
96
renderBackground(const QColor & bgColor)97 void PainterRenderer::renderBackground(const QColor &bgColor)
98 {
99 m_painter->setTransform(m_view->deviceTransform());
100 m_painter->fillRect(0, 0, m_view->screenWidth(), m_view->screenHeight(), bgColor);
101 }
102
beginPhase(SceneGraphItemPayload::RenderPhase phase)103 void PainterRenderer::beginPhase(SceneGraphItemPayload::RenderPhase phase)
104 {
105 switch (phase) {
106 case SceneGraphItemPayload::NoPhase:
107 Q_UNREACHABLE();
108 case SceneGraphItemPayload::FillPhase:
109 m_painter->setPen(Qt::NoPen);
110 m_painter->setTransform(m_view->sceneToScreenTransform() * m_view->deviceTransform());
111 m_painter->setClipRect(m_view->viewport().intersected(m_view->sceneBoundingBox()));
112 m_painter->setRenderHint(QPainter::Antialiasing, false);
113 break;
114 case SceneGraphItemPayload::CasingPhase:
115 case SceneGraphItemPayload::StrokePhase:
116 m_painter->setBrush(Qt::NoBrush);
117 m_painter->setTransform(m_view->sceneToScreenTransform() * m_view->deviceTransform());
118 m_painter->setClipRect(m_view->viewport().intersected(m_view->sceneBoundingBox()));
119 m_painter->setRenderHint(QPainter::Antialiasing, true);
120 break;
121 case SceneGraphItemPayload::LabelPhase:
122 m_painter->setTransform(m_view->deviceTransform());
123 m_painter->setRenderHint(QPainter::Antialiasing, true);
124 break;
125 }
126 }
127
renderPolygon(PolygonItem * item,SceneGraphItemPayload::RenderPhase phase)128 void PainterRenderer::renderPolygon(PolygonItem *item, SceneGraphItemPayload::RenderPhase phase)
129 {
130 if (phase == SceneGraphItemPayload::FillPhase) {
131 m_painter->setBrush(item->brush);
132 m_painter->drawPolygon(item->polygon);
133 } else {
134 auto p = item->pen;
135 p.setWidthF(mapToSceneWidth(item->pen.widthF(), item->penWidthUnit));
136 m_painter->setPen(p);
137 m_painter->drawPolygon(item->polygon);
138 }
139 }
140
renderMultiPolygon(MultiPolygonItem * item,SceneGraphItemPayload::RenderPhase phase)141 void PainterRenderer::renderMultiPolygon(MultiPolygonItem *item, SceneGraphItemPayload::RenderPhase phase)
142 {
143 if (phase == SceneGraphItemPayload::FillPhase) {
144 m_painter->setBrush(item->brush);
145 m_painter->drawPath(item->path);
146 } else {
147 auto p = item->pen;
148 p.setWidthF(mapToSceneWidth(item->pen.widthF(), item->penWidthUnit));
149 m_painter->setPen(p);
150 m_painter->drawPath(item->path);
151 }
152 }
153
renderPolyline(PolylineItem * item,SceneGraphItemPayload::RenderPhase phase)154 void PainterRenderer::renderPolyline(PolylineItem *item, SceneGraphItemPayload::RenderPhase phase)
155 {
156 if (phase == SceneGraphItemPayload::StrokePhase) {
157 auto p = item->pen;
158 p.setWidthF(mapToSceneWidth(item->pen.widthF(), item->penWidthUnit));
159 m_painter->setPen(p);
160 m_painter->drawPolyline(item->path);
161 } else {
162 auto p = item->casingPen;
163 p.setWidthF(mapToSceneWidth(item->pen.widthF(), item->penWidthUnit) + mapToSceneWidth(item->casingPen.widthF(), item->casingPenWidthUnit));
164 m_painter->setPen(p);
165 m_painter->drawPolyline(item->path);
166 }
167 }
168
renderLabel(LabelItem * item)169 void PainterRenderer::renderLabel(LabelItem *item)
170 {
171 m_painter->save();
172 m_painter->translate(m_view->mapSceneToScreen(item->pos));
173 m_painter->rotate(item->angle);
174
175 auto box = item->boundingRect();
176 box.moveCenter({0.0, item->offset});
177
178 // draw shield
179 // @see https://wiki.openstreetmap.org/wiki/MapCSS/0.2#Shield_properties
180 auto w = item->casingWidth + item->frameWidth + 2.0;
181 if (item->casingWidth > 0.0 && item->casingColor.alpha() > 0) {
182 m_painter->fillRect(box.adjusted(-w, -w, w, w), item->casingColor);
183 }
184 w -= item->casingWidth;
185 if (item->frameWidth > 0.0 && item->frameColor.alpha() > 0) {
186 m_painter->fillRect(box.adjusted(-w, -w, w, w), item->frameColor);
187 }
188 w -= item->frameWidth;
189 if (item->shieldColor.alpha() > 0) {
190 m_painter->fillRect(box.adjusted(-w, -w, w, w), item->shieldColor);
191 }
192
193 // draw icon
194 if (!item->icon.isNull()) {
195 QRectF iconRect(QPointF(0.0, 0.0), item->iconSize);
196 iconRect.moveCenter(QPointF(0.0, -((box.height() - item->iconSize.height()) / 2.0) + item->offset));
197 item->icon.paint(m_painter, iconRect.toRect());
198 }
199 box.moveTop(box.top() + item->iconSize.height());
200
201 // draw text halo
202 if (item->haloRadius > 0.0 && item->haloColor.alphaF() > 0.0) {
203 const auto haloBox = box.adjusted(-item->haloRadius, -item->haloRadius, item->haloRadius, item->haloRadius);
204 QImage haloBuffer(haloBox.size().toSize(), QImage::Format_ARGB32);
205 haloBuffer.fill(Qt::transparent);
206 QPainter haloPainter(&haloBuffer);
207 haloPainter.setPen(item->haloColor);
208 haloPainter.setFont(item->font);
209 auto haloTextRect = box;
210 haloTextRect.moveTopLeft({item->haloRadius, item->haloRadius});
211 haloPainter.drawStaticText(haloTextRect.topLeft(), item->text);
212 StackBlur::blur(haloBuffer, item->haloRadius);
213 haloPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
214 haloPainter.fillRect(haloBuffer.rect(), item->haloColor);
215 m_painter->drawImage(haloBox, haloBuffer);
216 }
217
218 // draw text
219 if (!item->text.text().isEmpty()) {
220 m_painter->setPen(item->color);
221 m_painter->setFont(item->font);
222 m_painter->drawStaticText(box.topLeft(), item->text);
223 }
224
225 m_painter->restore();
226 }
227
renderForeground(const QColor & bgColor)228 void PainterRenderer::renderForeground(const QColor &bgColor)
229 {
230 // fade out the map at the end of the scene box, to indicate you can't scroll further
231 m_painter->setTransform(m_view->deviceTransform());
232 m_painter->setClipRect(m_view->mapSceneToScreen(m_view->viewport()));
233 const auto borderWidth = 10;
234
235 QColor c(bgColor);
236 c.setAlphaF(0.75);
237 QLinearGradient gradient;
238 gradient.setColorAt(0, bgColor);
239 gradient.setColorAt(0.2, c);
240 gradient.setColorAt(1, Qt::transparent);
241
242 auto r = m_view->mapSceneToScreen(m_view->sceneBoundingBox());
243 r.setBottom(r.top() + borderWidth);
244 gradient.setStart(r.topLeft());
245 gradient.setFinalStop(r.bottomLeft());
246 m_painter->fillRect(r, gradient);
247
248 r = m_view->mapSceneToScreen(m_view->sceneBoundingBox());
249 r.setTop(r.bottom() - borderWidth);
250 gradient.setStart(r.bottomLeft());
251 gradient.setFinalStop(r.topLeft());
252 m_painter->fillRect(r, gradient);
253
254 r = m_view->mapSceneToScreen(m_view->sceneBoundingBox());
255 r.setRight(r.left() + borderWidth);
256 gradient.setStart(r.topLeft());
257 gradient.setFinalStop(r.topRight());
258 m_painter->fillRect(r, gradient);
259
260 r = m_view->mapSceneToScreen(m_view->sceneBoundingBox());
261 r.setLeft(r.right() - borderWidth);
262 gradient.setStart(r.topRight());
263 gradient.setFinalStop(r.topLeft());
264 m_painter->fillRect(r, gradient);
265 }
266
endRender()267 void PainterRenderer::endRender()
268 {
269 m_painter->restore();
270 }
271
mapToSceneWidth(double width,Unit unit) const272 double PainterRenderer::mapToSceneWidth(double width, Unit unit) const
273 {
274 switch (unit) {
275 case Unit::Pixel:
276 return m_view->mapScreenDistanceToSceneDistance(width);
277 case Unit::Meter:
278 return m_view->mapMetersToScene(width);
279 }
280
281 return width;
282 }
283