7#include "painterrenderer.h"
8#include "stackblur_p.h"
9#include "render-logging.h"
11#include <KOSMIndoorMap/SceneGraph>
12#include <KOSMIndoorMap/View>
15#include <QElapsedTimer>
16#include <QFontMetricsF>
17#include <QGuiApplication>
19#include <QLinearGradient>
26PainterRenderer::PainterRenderer() =
default;
27PainterRenderer::~PainterRenderer() =
default;
29void PainterRenderer::setPainter(
QPainter *painter)
36 QElapsedTimer frameTimer;
43 for (
const auto &layerOffsets : sg.layerOffsets()) {
44 const auto layerBegin = sg.itemsBegin(layerOffsets);
45 const auto layerEnd = sg.itemsEnd(layerOffsets);
49 m_renderBatch.clear();
50 m_renderBatch.reserve(layerOffsets.second - layerOffsets.first);
51 const QRectF screenRect(QPointF(0, 0), QSizeF(m_view->screenWidth(), m_view->screenHeight()));
52 for (
auto it = layerBegin; it != layerEnd; ++it) {
53 if ((*it).payload->inSceneSpace() && m_view->viewport().intersects((*it).payload->boundingRect(view))) {
54 m_renderBatch.push_back((*it).payload.get());
56 if ((*it).payload->inHUDSpace()) {
57 auto bbox = (*it).payload->boundingRect(view);
58 bbox.moveCenter(m_view->mapSceneToScreen(bbox.center()));
59 if (screenRect.intersects(bbox)) {
60 m_renderBatch.push_back((*it).payload.get());
65 for (
auto phase : {SceneGraphItemPayload::FillPhase, SceneGraphItemPayload::CasingPhase, SceneGraphItemPayload::StrokePhase, SceneGraphItemPayload::IconPhase, SceneGraphItemPayload::LabelPhase}) {
68 for (
auto it = m_renderBatch.begin(); it != m_renderBatch.end(); ++it) {
69 const auto &item = (*it);
70 if ((item->renderPhases() & phase) == 0) {
74 if (
auto i =
dynamic_cast<PolygonItem*
>(item)) {
75 renderPolygon(i, phase);
76 }
else if (
auto i =
dynamic_cast<MultiPolygonItem*
>(item)) {
77 renderMultiPolygon(i, phase);
78 }
else if (
auto i =
dynamic_cast<PolylineItem*
>(item)) {
79 renderPolyline(i, phase);
80 }
else if (
auto i =
dynamic_cast<LabelItem*
>(item)) {
82 if (phase == SceneGraphItemPayload::IconPhase) {
84 renderLabel(i, phase);
86 }
else if (phase == SceneGraphItemPayload::LabelPhase) {
88 renderLabel(i, phase);
92 qCritical() <<
"Unsupported scene graph item!";
102 qCDebug(RenderLog) <<
"rendering took:" << frameTimer.
elapsed() <<
"ms for" << sg.items().size() <<
"items on" << sg.layerOffsets().size() <<
"layers";
105void PainterRenderer::beginRender()
110void PainterRenderer::renderBackground(
const QColor &bgColor)
112 m_painter->setTransform(m_view->deviceTransform());
113 m_painter->fillRect(0, 0, m_view->screenWidth(), m_view->screenHeight(), bgColor);
119 case SceneGraphItemPayload::NoPhase:
121 case SceneGraphItemPayload::FillPhase:
123 m_painter->setTransform(m_view->sceneToScreenTransform() * m_view->deviceTransform());
124 m_painter->setClipRect(m_view->viewport().intersected(m_view->sceneBoundingBox()));
127 case SceneGraphItemPayload::CasingPhase:
128 case SceneGraphItemPayload::StrokePhase:
130 m_painter->setTransform(m_view->sceneToScreenTransform() * m_view->deviceTransform());
131 m_painter->setClipRect(m_view->viewport().intersected(m_view->sceneBoundingBox()));
134 case SceneGraphItemPayload::IconPhase:
135 case SceneGraphItemPayload::LabelPhase:
136 m_painter->setTransform(m_view->deviceTransform());
146 case SceneGraphItemPayload::NoPhase:
148 case SceneGraphItemPayload::FillPhase:
149 case SceneGraphItemPayload::CasingPhase:
150 case SceneGraphItemPayload::StrokePhase:
152 case SceneGraphItemPayload::IconPhase:
156 for (
auto it = m_renderBatch.rbegin(); it != m_renderBatch.rend(); ++it) {
157 if (((*it)->renderPhases() & SceneGraphItemPayload::IconPhase) == 0) {
160 const auto item =
dynamic_cast<LabelItem*
>(*it);
164 item->iconHidden =
false;
165 if (item->allowIconOverlap) {
170 if (item->hasShield()) {
171 bbox = item->shieldHitBox(m_view);
173 bbox = item->iconHitBox(m_view);
176 for (
auto it2 = it.base(); it2 != m_renderBatch.end(); ++it2) {
177 if (((*it2)->renderPhases() & SceneGraphItemPayload::IconPhase) == 0) {
180 const auto otherItem =
dynamic_cast<LabelItem*
>((*it2));
181 if (!otherItem || otherItem->allowIconOverlap) {
186 if (otherItem->hasShield()) {
187 bbox2 = otherItem->shieldHitBox(m_view);
189 bbox2 = otherItem->iconHitBox(m_view);
193 item->iconHidden =
true;
199 case SceneGraphItemPayload::LabelPhase:
204 for (
auto it = m_renderBatch.rbegin(); it != m_renderBatch.rend(); ++it) {
205 if (((*it)->renderPhases() & SceneGraphItemPayload::LabelPhase) == 0) {
208 const auto item =
dynamic_cast<LabelItem*
>(*it);
212 item->textHidden =
false;
213 if (item->allowTextOverlap) {
216 if (item->iconHidden) {
217 item->textHidden =
true;
221 const QRectF bbox = item->textHitBox(m_view);
226 for (
auto it2 = m_renderBatch.begin(); it2 != m_renderBatch.end(); ++it2) {
227 if (it2 == std::prev(it.base())) {
230 const auto p = (*it2)->renderPhases();
231 if ((p & SceneGraphItemPayload::IconPhase) == 0 && ((p & SceneGraphItemPayload::LabelPhase) == 0 || it2 < it.base())) {
235 const auto otherItem =
dynamic_cast<LabelItem*
>((*it2));
236 if (!otherItem || otherItem->iconHidden || otherItem->allowTextOverlap) {
240 if (otherItem->hasShield()) {
241 if (otherItem->shieldHitBox(m_view).intersects(bbox)) {
242 item->textHidden =
true;
247 if (it2 >= it.base() && otherItem->hasText() && !otherItem->textHidden && otherItem->textHitBox(m_view).intersects(bbox)) {
248 item->textHidden =
true;
251 if (otherItem->hasIcon() && otherItem->iconHitBox(m_view).intersects(bbox)) {
252 item->textHidden =
true;
261static inline void drawGeometry(QPainter *painter,
const QPolygonF &polygon) { painter->
drawPolygon(polygon,
Qt::OddEvenFill); }
262static inline void drawGeometry(QPainter *painter,
const QPainterPath &path) { painter->
drawPath(path); }
265inline void PainterRenderer::renderPolygonFill(
PolygonBaseItem *item,
const T &geom)
268 m_painter->setBrush(item->fillBrush);
269 drawGeometry(m_painter, geom);
273 m_painter->setOpacity(item->textureBrush.
color().
alphaF());
274 m_painter->setBrush(item->textureBrush);
275 drawGeometry(m_painter, geom);
276 m_painter->setOpacity(1.0);
281inline void PainterRenderer::renderPolygonCasing(
PolygonBaseItem *item,
const T &geom)
283 auto p = item->casingPen;
284 p.
setWidthF(mapToSceneWidth(item->casingPen.
widthF(), item->casingPenWidthUnit));
285 m_painter->setPen(p);
286 drawGeometry(m_painter, geom);
290inline void PainterRenderer::renderPolygonLine(
PolygonBaseItem *item,
const T &geom)
294 m_painter->setPen(p);
295 drawGeometry(m_painter, geom);
301 if (phase == SceneGraphItemPayload::CasingPhase) {
302 renderPolygonCasing(item, item->polygon);
303 }
else if (phase == SceneGraphItemPayload::StrokePhase) {
305 renderPolygonFill(item, item->polygon);
309 if (phase == SceneGraphItemPayload::FillPhase) {
310 renderPolygonFill(item, item->polygon);
311 }
else if (phase == SceneGraphItemPayload::StrokePhase) {
312 renderPolygonLine(item, item->polygon);
320 if (phase == SceneGraphItemPayload::CasingPhase) {
321 renderPolygonCasing(item, item->path);
322 }
else if (phase == SceneGraphItemPayload::StrokePhase) {
324 renderPolygonFill(item, item->path);
328 if (phase == SceneGraphItemPayload::FillPhase) {
329 renderPolygonFill(item, item->path);
330 }
else if (phase == SceneGraphItemPayload::StrokePhase) {
331 renderPolygonLine(item, item->path);
338 if (phase == SceneGraphItemPayload::StrokePhase) {
342 const auto wt = m_painter->transform();
343 m_painter->resetTransform();
344 p.setWidthF(mapToScreenWidth(item->pen.
widthF(), item->penWidthUnit));
346 Q_ASSERT(item->path.
size() > 1);
347 for (
auto it = item->path.
constBegin(); it != std::prev(item->path.
constEnd()); ++it) {
348 QLineF line(wt.map(*it), wt.map(*std::next(it)));
349 b.setTransform(QTransform().translate(wt.map(*it).x(), wt.map(*it).y()).rotate(-line.angle()).translate(0.0, -p.widthF() / 2.0));
351 m_painter->setPen(p);
352 m_painter->drawLine(line);
354 m_painter->restore();
356 p.setWidthF(mapToSceneWidth(item->pen.
widthF(), item->penWidthUnit));
357 m_painter->setPen(p);
358 m_painter->drawPolyline(item->path);
361 auto p = item->casingPen;
362 p.
setWidthF(mapToSceneWidth(item->pen.
widthF(), item->penWidthUnit) + mapToSceneWidth(item->casingPen.
widthF(), item->casingPenWidthUnit));
363 m_painter->setPen(p);
364 m_painter->drawPolyline(item->path);
371 m_painter->translate(m_view->mapSceneToScreen(item->pos));
372 m_painter->rotate(item->angle);
378 QSizeF iconOutputSize = item->iconOutputSize(m_view);
379 if (!item->icon.
isNull()) {
380 box.moveTop(-iconOutputSize.
height() / 2.0);
385 m_painter->drawRect(item->iconHitBox(m_view).
translated(-m_view->mapSceneToScreen(item->pos)));
387 m_painter->drawRect(item->textHitBox(m_view).
translated(-m_view->mapSceneToScreen(item->pos)));
389 m_painter->drawRect(item->shieldHitBox(m_view).
translated(-m_view->mapSceneToScreen(item->pos)));
394 auto w = item->casingWidth + item->frameWidth + 2.0;
395 if (item->casingWidth > 0.0 && item->casingColor.
alpha() > 0) {
396 m_painter->fillRect(box.adjusted(-w, -w, w, w), item->casingColor);
398 w -= item->casingWidth;
399 if (item->frameWidth > 0.0 && item->frameColor.
alpha() > 0) {
400 m_painter->fillRect(box.adjusted(-w, -w, w, w), item->frameColor);
402 w -= item->frameWidth;
403 if (item->shieldColor.
alpha() > 0) {
404 m_painter->fillRect(box.adjusted(-w, -w, w, w), item->shieldColor);
408 if (!iconOutputSize.
isNull() && phase == SceneGraphItemPayload::IconPhase) {
409 QRectF iconRect(QPointF(-iconOutputSize.
width() / 2.0, -iconOutputSize.
height() / 2.0), iconOutputSize);
410 m_painter->setOpacity(item->iconOpacity);
411 item->icon.
paint(m_painter, iconRect.toRect());
412 m_painter->setOpacity(1.0);
414 box.moveTop(box.top() + item->textOffset);
418 box.moveCenter({0.0, box.center().y()});
420 if (item->hasText() && (phase == SceneGraphItemPayload::LabelPhase || item->hasShield())) {
422 if (item->haloRadius > 0.0 && item->haloColor.
alphaF() > 0.0) {
423 const auto haloBox = box.adjusted(-item->haloRadius, -item->haloRadius, item->haloRadius, item->haloRadius);
426 QPainter haloPainter(&haloBuffer);
427 haloPainter.setPen(item->haloColor);
428 haloPainter.setFont(item->font);
429 auto haloTextRect = box;
430 haloTextRect.moveTopLeft({item->haloRadius, item->haloRadius});
431 if (!item->isComplexText) {
432 haloPainter.drawStaticText(haloTextRect.topLeft(), item->text);
434 haloPainter.drawText(haloTextRect, item->text.
text(), item->text.
textOption());
436 StackBlur::blur(haloBuffer, item->haloRadius);
438 haloPainter.fillRect(haloBuffer.rect(), item->haloColor);
439 m_painter->drawImage(haloBox, haloBuffer);
443 m_painter->setPen(item->color);
444 m_painter->setFont(item->font);
445 if (!item->isComplexText) {
446 m_painter->drawStaticText(box.topLeft(), item->text);
448 m_painter->drawText(box, item->text.
text(), item->text.
textOption());
452 m_painter->restore();
455void PainterRenderer::renderForeground(
const QColor &bgColor)
458 m_painter->setTransform(m_view->deviceTransform());
459 m_painter->setClipRect(m_view->mapSceneToScreen(m_view->viewport()));
460 const auto borderWidth = 10;
464 QLinearGradient gradient;
469 auto r = m_view->mapSceneToScreen(m_view->sceneBoundingBox());
470 r.setBottom(r.top() + borderWidth);
473 m_painter->fillRect(r, gradient);
475 r = m_view->mapSceneToScreen(m_view->sceneBoundingBox());
476 r.setTop(r.bottom() - borderWidth);
479 m_painter->fillRect(r, gradient);
481 r = m_view->mapSceneToScreen(m_view->sceneBoundingBox());
482 r.setRight(r.left() + borderWidth);
485 m_painter->fillRect(r, gradient);
487 r = m_view->mapSceneToScreen(m_view->sceneBoundingBox());
488 r.setLeft(r.right() - borderWidth);
491 m_painter->fillRect(r, gradient);
494void PainterRenderer::endRender()
496 m_painter->restore();
499double PainterRenderer::mapToSceneWidth(
double width,
Unit unit)
const
503 return m_view->mapScreenDistanceToSceneDistance(width);
505 return m_view->mapMetersToScene(width);
511double PainterRenderer::mapToScreenWidth(
double width,
Unit unit)
const
515 return width * m_view->deviceTransform().m11();
517 return m_view->mapMetersToScreen(width);
523QTransform PainterRenderer::brushTransform()
const
531 constexpr const auto TextureZoomSteps = 5.0;
532 auto viewport = m_view->viewportForZoom(std::round(m_view->zoomLevel() * TextureZoomSteps) / TextureZoomSteps,
533 QPointF(m_view->screenWidth() / 2.0, m_view->screenHeight() / 2.0));
535 t.
scale(viewport.width() / m_view->screenWidth(), viewport.height() / m_view->screenHeight());
QRectF boundingRect(const View *view) const override
Bounding box of this item in scene coordinates.
Multi-polygon item, used for polygons with "holes" in them.
Base item for filled polygons.
bool useCasingFillMode() const
Render like lines, ie casing and filling in the stroke phase, rather than the default.
A path/way/line item in the scenegraph.
RenderPhase
See MapCSS spec: "Within a layer, first all fills are rendered, then all casings, then all strokes,...
Scene graph of the currently displayed level.
QColor backgroundColor() const
Canvas background color.
View transformations and transformation manipulation.
OSM-based multi-floor indoor maps for buildings.
Unit
Unit for geometry sizes.
const QColor & color() const const
Qt::BrushStyle style() const const
float alphaF() const const
qint64 elapsed() const const
void setColorAt(qreal position, const QColor &color)
bool isNull() const const
void paint(QPainter *painter, const QRect &rect, Qt::Alignment alignment, Mode mode, State state) const const
void setFinalStop(const QPointF &stop)
void setStart(const QPointF &start)
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype size() const const
void drawPath(const QPainterPath &path)
void drawPolygon(const QPoint *points, int pointCount, Qt::FillRule fillRule)
void setWidthF(qreal width)
qreal widthF() const const
bool intersects(const QRectF &rectangle) const const
void moveCenter(const QPointF &position)
QRectF translated(const QPointF &offset) const const
qreal height() const const
bool isNull() const const
qreal width() const const
QSizeF size() const const
QString text() const const
QTextOption textOption() const const