KOSMIndoorMap

painterrenderer.cpp
1 /*
2  SPDX-FileCopyrightText: 2020 Volker Krause <[email protected]>
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 
27 void PainterRenderer::setPainter(QPainter *painter)
28 {
29  m_painter = painter;
30 }
31 
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 
92 void PainterRenderer::beginRender()
93 {
94  m_painter->save();
95 }
96 
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 
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 
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 
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 
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 
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 
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 
267 void PainterRenderer::endRender()
268 {
269  m_painter->restore();
270 }
271 
272 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 }
OSM-based multi-floor indoor maps for buildings.
A path/way/line item in the scenegraph.
void setTransform(const QTransform &transform, bool combine)
double mapMetersToScene(double meters) const
Returns how many units in scene coordinate represent the distance of meters in the current view trans...
Definition: view.cpp:245
void fillRect(const QRectF &rectangle, const QBrush &brush)
qreal alphaF() const const
void setRenderHint(QPainter::RenderHint hint, bool on)
void drawStaticText(const QPointF &topLeftPosition, const QStaticText &staticText)
Scene graph of the currently displayed level.
Definition: scenegraph.h:28
View transformations and transformation manipulation.
Definition: view.h:39
void setColorAt(qreal position, const QColor &color)
QColor backgroundColor() const
Canvas background color.
Definition: scenegraph.cpp:91
QTransform sceneToScreenTransform() const
The transformation to apply to scene coordinate to get to the view on screen.
Definition: view.cpp:200
A text or item label.
void drawPolyline(const QPointF *points, int pointCount)
void save()
Multi-polygon item, used for polygons with "holes" in them.
void drawPolygon(const QPointF *points, int pointCount, Qt::FillRule fillRule)
bool intersects(const QRectF &rectangle) const const
QRectF intersected(const QRectF &rectangle) const const
void rotate(qreal angle)
QTransform deviceTransform() const
Device tranformation for manual high DPI scaling.
Definition: view.cpp:299
void setFont(const QFont &font)
void fill(uint pixelValue)
void setPen(const QColor &color)
bool isEmpty() const const
void setWidthF(qreal width)
void setBrush(const QBrush &brush)
int screenWidth() const
Screen-space sizes, ie the size of the on-screen area used for displaying.
Definition: view.cpp:63
void paint(QPainter *painter, const QRect &rect, Qt::Alignment alignment, QIcon::Mode mode, QIcon::State state) const const
int alpha() const const
void moveCenter(const QPointF &position)
void setFinalStop(const QPointF &stop)
CompositionMode_SourceIn
QRectF sceneBoundingBox() const
The bounding box of the scene.
Definition: view.cpp:135
void restore()
void drawImage(const QRectF &target, const QImage &image, const QRectF &source, Qt::ImageConversionFlags flags)
void setClipRect(const QRectF &rectangle, Qt::ClipOperation operation)
void drawPath(const QPainterPath &path)
bool isNull() const const
QString text() const const
A single filled polygon.
Unit
Unit for geometry sizes.
qreal widthF() const const
void translate(const QPointF &offset)
void setStart(const QPointF &start)
qreal height() const const
transparent
qint64 elapsed() const const
RenderPhase
See MapCSS spec: "Within a layer, first all fills are rendered, then all casings, then all strokes...
QRectF boundingRect() const override
Bounding box of this item in scene coordinates.
double mapScreenDistanceToSceneDistance(double distance) const
Converts a distance in screen coordinates to a distance in scene coordinates.
Definition: view.cpp:184
QRectF viewport() const
The sub-rect of the scene bounding box currently displayed.
Definition: view.cpp:124
QPointF mapSceneToScreen(QPointF scenePos) const
Converts a point in scene coordinates to screen coordinates.
Definition: view.cpp:168
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Oct 23 2021 23:03:45 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.