Use fuzzy text bounding rect computation to improve performance

Do not use QGraphicsScene as it is not thread-safe
This commit is contained in:
Martin Tůma 2018-11-11 14:10:38 +01:00
parent 078fc93783
commit d6f6a0eb3d
9 changed files with 207 additions and 85 deletions

View File

@ -1,7 +1,7 @@
TARGET = pbf
TEMPLATE = lib
CONFIG += plugin
QT += gui widgets
QT += gui
PROTOS = protobuf/vector_tile.proto
include(protobuf/vector_tile.pri)
@ -17,8 +17,9 @@ HEADERS += src/pbfhandler.h \
src/tile.h \
src/function.h \
src/textpathitem.h \
src/textitem.h \
src/font.h
src/textpointitem.h \
src/font.h \
src/textitem.h
SOURCES += src/pbfplugin.cpp \
src/pbfhandler.cpp \
src/gzip.cpp \
@ -28,7 +29,7 @@ SOURCES += src/pbfplugin.cpp \
src/text.cpp \
src/function.cpp \
src/textpathitem.cpp \
src/textitem.cpp \
src/textpointitem.cpp \
src/font.cpp
RESOURCES += pbfplugin.qrc

View File

@ -1,8 +1,7 @@
#include <QGraphicsPixmapItem>
#include <QFontMetrics>
#include <QPainter>
#include "text.h"
#include "textitem.h"
#include "textpointitem.h"
#include "textpathitem.h"
@ -134,16 +133,32 @@ static bool reverse(const QPainterPath &path)
return (angle > 90 && angle < 270) ? true : false;
}
Text::~Text()
{
for (int i = 0; i < _items.size(); i++)
delete _items[i];
}
void Text::render(QPainter *painter)
{
for (int i = 0; i < _items.size(); i++) {
const TextItem *ti = _items.at(i);
if (ti->isVisible() && _sceneRect.intersects(ti->boundingRect()))
ti->paint(painter);
}
}
void Text::addLabel(const QString &text, const QPointF &pos, const QFont &font,
const QPen &pen, qreal maxTextWidth)
{
if (text.isEmpty())
return;
TextItem *ti = new TextItem(text, pos, font, maxTextWidth);
TextPointItem *ti = new TextPointItem(text, pos, font, maxTextWidth);
ti->setPen(pen);
addItem(ti);
QList<QGraphicsItem*> ci = collidingItems(ti);
QList<TextItem*> ci = collidingItems(ti);
for (int i = 0; i < ci.size(); i++)
ci[i]->setVisible(false);
}
@ -163,21 +178,37 @@ void Text::addLabel(const QString &text, const QPainterPath &path,
return;
QPainterPath tp(textPath(path, textWidth, maxAngle, fm.averageCharWidth(),
sceneRect()));
_sceneRect));
if (tp.isEmpty())
return;
TextPathItem *pi = new TextPathItem(text, reverse(tp) ? tp.toReversed()
: tp, font);
addItem(pi);
if (!sceneRect().contains(pi->sceneBoundingRect())) {
if (!_sceneRect.contains(pi->boundingRect())) {
delete pi;
return;
}
pi->setPen(pen);
QList<QGraphicsItem*> ci = collidingItems(pi);
addItem(pi);
QList<TextItem*> ci = collidingItems(pi);
for (int j = 0; j < ci.size(); j++)
ci[j]->setVisible(false);
}
QList<TextItem*> Text::collidingItems(const TextItem *item) const
{
QList<TextItem*> list;
if (!item->isVisible())
return list;
for (int i = 0; i < _items.size();i ++) {
const TextItem *ti = _items.at(i);
if (ti != item && ti->isVisible() && ti->collidesWithItem(item))
list.append(const_cast<TextItem*>(ti));
}
return list;
}

View File

@ -1,18 +1,27 @@
#ifndef TEXT_H
#define TEXT_H
#include <QGraphicsScene>
#include "textitem.h"
class Text : public QGraphicsScene
class Text
{
public:
Text(int size, QObject *parent = 0) : QGraphicsScene(parent)
{setSceneRect(0, 0, size, size);}
Text(int size) : _sceneRect(QRectF(0, 0, size, size)) {}
~Text();
void render(QPainter *painter);
void addLabel(const QString &text, const QPointF &pos, const QFont &font,
const QPen &pen, qreal maxTextWidth);
void addLabel(const QString &text, const QPainterPath &path,
const QFont &font, const QPen &pen, qreal maxAngle);
private:
void addItem(TextItem *item) {_items.append(item);}
QList<TextItem *> collidingItems(const TextItem *item) const;
QRectF _sceneRect;
QList<TextItem *> _items;
};
#endif // TEXT_H

View File

@ -1,39 +0,0 @@
#include <QPainter>
#include "textitem.h"
#define FLAGS (Qt::AlignCenter | Qt::TextWordWrap | Qt::TextDontClip)
TextItem::TextItem(const QString &text, const QPointF &pos, const QFont &font,
int maxTextWidth, QGraphicsItem *parent) : QGraphicsItem(parent), _text(text),
_font(font)
{
QFontMetrics fm(font);
int limit = font.pixelSize() * maxTextWidth;
// Italic fonts overflow the computed bounding rect, so reduce it
// a little bit.
if (font.italic())
limit -= font.pixelSize() / 2.0;
QRect br = fm.boundingRect(QRect(0, 0, limit, 0), FLAGS, text);
Q_ASSERT(br.isValid());
// Expand the bounding rect back to the real content size
if (font.italic())
br.adjust(-font.pixelSize() / 4.0, 0, font.pixelSize() / 4.0, 0);
setPos((pos - QPointF(br.width() / 2.0, br.height() / 2.0)).toPoint());
_boundingRect = QRectF(0, 0, br.width(), br.height());
}
void TextItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
painter->setFont(_font);
painter->setPen(_pen);
painter->drawText(_boundingRect, FLAGS, _text);
//painter->setPen(Qt::red);
//painter->drawRect(_boundingRect);
}

View File

@ -1,28 +1,36 @@
#ifndef TEXTITEM_H
#define TEXTITEM_H
#include <QGraphicsItem>
#include <QPen>
#include <QFont>
#include <QString>
#include <QPainterPath>
class TextItem : public QGraphicsItem
#include <QDebug>
class TextItem
{
public:
TextItem(const QString &text, const QPointF &pos, const QFont &font,
int maxTextWidth, QGraphicsItem *parent = 0);
TextItem() : _visible(true) {}
virtual ~TextItem() {}
QRectF boundingRect() const {return _boundingRect;}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget);
virtual QPainterPath shape() const = 0;
virtual QRectF boundingRect() const = 0;
virtual void paint(QPainter *painter) const = 0;
void setPen(const QPen &pen) {_pen = pen;}
bool isVisible() const {return _visible;}
void setVisible(bool visible) {_visible = visible;}
bool collidesWithItem(const TextItem *other) const
{
QRectF r1(boundingRect());
QRectF r2(other->boundingRect());
if (r1.isEmpty() || r2.isEmpty() || !r1.intersects(r2))
return false;
return other->shape().intersects(shape());
}
private:
QString _text;
QRectF _boundingRect;
QFont _font;
QPen _pen;
bool _visible;
};
#endif // TEXTITEM_H

View File

@ -4,22 +4,18 @@
TextPathItem::TextPathItem(const QString &text, const QPainterPath &path,
const QFont &font, QGraphicsItem *parent) : QGraphicsItem(parent),
_text(text), _path(path), _font(font)
const QFont &font) : _text(text), _path(path), _font(font)
{
QFontMetrics fm(font);
QPainterPathStroker s;
s.setWidth(fm.height());
s.setCapStyle(Qt::FlatCap);
_shape = s.createStroke(path).simplified();
_boundingRect = _shape.boundingRect();
}
void TextPathItem::paint(QPainter *painter,
const QStyleOptionGraphicsItem *option, QWidget *widget)
void TextPathItem::paint(QPainter *painter) const
{
Q_UNUSED(option);
Q_UNUSED(widget);
QFontMetrics fm(_font);
int textWidth = fm.width(_text);

View File

@ -1,21 +1,19 @@
#ifndef TEXTPATHITEM_H
#define TEXTPATHITEM_H
#include <QGraphicsItem>
#include <QPainterPath>
#include <QFont>
#include <QString>
#include "textitem.h"
class TextPathItem : public QGraphicsItem
class TextPathItem : public TextItem
{
public:
TextPathItem(const QString &text, const QPainterPath &path,
const QFont &font, QGraphicsItem *parent = 0);
const QFont &font);
QPainterPath shape() const {return _shape;}
QRectF boundingRect() const {return _shape.boundingRect();}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget);
QRectF boundingRect() const {return _boundingRect;}
void paint(QPainter *painter) const;
void setPen(const QPen &pen) {_pen = pen;}
@ -23,6 +21,7 @@ private:
QString _text;
QPainterPath _path;
QPainterPath _shape;
QRectF _boundingRect;
QFont _font;
QPen _pen;
};

88
src/textpointitem.cpp Normal file
View File

@ -0,0 +1,88 @@
#include <QPainter>
#include <QtMath>
#include "textpointitem.h"
#define FLAGS (Qt::AlignCenter | Qt::TextWordWrap | Qt::TextDontClip)
static QRectF fuzzyBoundingRect(const QString &str, const QFont &font,
int maxTextWidth)
{
int limit = font.pixelSize() * maxTextWidth;
qreal cw = font.pixelSize() * 0.6;
qreal lh = font.pixelSize() * 1.25;
int width = 0, lines = 0;
QStringList l(str.split('\n'));
for (int i = 0; i < l.size(); i++) {
int lw = (int)(l.at(i).length() * cw);
if (lw > limit) {
l[i].replace('-', ' ');
l[i].replace('/', ' ');
QStringList words(l.at(i).split(' '));
int pl = 0;
for (int j = 0; j < words.size(); j++) {
int wl = (int)(words.at(j).length() * cw);
if (wl + pl < limit) {
pl += wl + cw;
} else {
if (wl > limit) {
if (pl > 0)
lines++;
} else
lines++;
width = qMax(width, qMax(pl, wl));
pl = wl;
}
}
width = qMax(width, pl);
lines++;
} else {
width = qMax(width, lw);
lines++;
}
}
return QRectF(0, 0, width, lines * lh);
}
/*
static QRectF exactBoundingRect(const QString &str, const QFont &font,
int maxTextWidth)
{
QFontMetrics fm(font);
int limit = font.pixelSize() * maxTextWidth;
// Italic fonts overflow the computed bounding rect, so reduce it
// a little bit.
if (font.italic())
limit -= font.pixelSize() / 2.0;
QRect br = fm.boundingRect(QRect(0, 0, limit, 0), FLAGS, str);
Q_ASSERT(br.isValid());
// Expand the bounding rect back to the real content size
if (font.italic())
br.adjust(-font.pixelSize() / 4.0, 0, font.pixelSize() / 4.0, 0);
return br;
}
*/
TextPointItem::TextPointItem(const QString &text, const QPointF &pos,
const QFont &font, int maxTextWidth) :_text(text), _font(font)
{
_boundingRect = fuzzyBoundingRect(text, font, maxTextWidth);
//_boundingRect = exactBoundingRect(text, font, maxTextWidth);
_boundingRect.moveCenter(pos);
_shape.addRect(_boundingRect);
}
void TextPointItem::paint(QPainter *painter) const
{
painter->setFont(_font);
painter->setPen(_pen);
painter->drawText(_boundingRect, FLAGS, _text);
//painter->setPen(Qt::red);
//painter->drawRect(_boundingRect);
}

29
src/textpointitem.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef TEXTPOINTITEM_H
#define TEXTPOINTITEM_H
#include <QPen>
#include <QFont>
#include <QString>
#include "textitem.h"
class TextPointItem : public TextItem
{
public:
TextPointItem(const QString &text, const QPointF &pos, const QFont &font,
int maxTextWidth);
QRectF boundingRect() const {return _boundingRect;}
QPainterPath shape() const {return _shape;}
void paint(QPainter *painter) const;
void setPen(const QPen &pen) {_pen = pen;}
private:
QString _text;
QPainterPath _shape;
QRectF _boundingRect;
QFont _font;
QPen _pen;
};
#endif // TEXTPOINTITEM_H