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

View File

@ -1,8 +1,7 @@
#include <QGraphicsPixmapItem>
#include <QFontMetrics> #include <QFontMetrics>
#include <QPainter> #include <QPainter>
#include "text.h" #include "text.h"
#include "textitem.h" #include "textpointitem.h"
#include "textpathitem.h" #include "textpathitem.h"
@ -134,16 +133,32 @@ static bool reverse(const QPainterPath &path)
return (angle > 90 && angle < 270) ? true : false; 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, void Text::addLabel(const QString &text, const QPointF &pos, const QFont &font,
const QPen &pen, qreal maxTextWidth) const QPen &pen, qreal maxTextWidth)
{ {
if (text.isEmpty()) if (text.isEmpty())
return; return;
TextItem *ti = new TextItem(text, pos, font, maxTextWidth); TextPointItem *ti = new TextPointItem(text, pos, font, maxTextWidth);
ti->setPen(pen); ti->setPen(pen);
addItem(ti); addItem(ti);
QList<QGraphicsItem*> ci = collidingItems(ti); QList<TextItem*> ci = collidingItems(ti);
for (int i = 0; i < ci.size(); i++) for (int i = 0; i < ci.size(); i++)
ci[i]->setVisible(false); ci[i]->setVisible(false);
} }
@ -163,21 +178,37 @@ void Text::addLabel(const QString &text, const QPainterPath &path,
return; return;
QPainterPath tp(textPath(path, textWidth, maxAngle, fm.averageCharWidth(), QPainterPath tp(textPath(path, textWidth, maxAngle, fm.averageCharWidth(),
sceneRect())); _sceneRect));
if (tp.isEmpty()) if (tp.isEmpty())
return; return;
TextPathItem *pi = new TextPathItem(text, reverse(tp) ? tp.toReversed() TextPathItem *pi = new TextPathItem(text, reverse(tp) ? tp.toReversed()
: tp, font); : tp, font);
addItem(pi); if (!_sceneRect.contains(pi->boundingRect())) {
if (!sceneRect().contains(pi->sceneBoundingRect())) {
delete pi; delete pi;
return; return;
} }
pi->setPen(pen); pi->setPen(pen);
QList<QGraphicsItem*> ci = collidingItems(pi); addItem(pi);
QList<TextItem*> ci = collidingItems(pi);
for (int j = 0; j < ci.size(); j++) for (int j = 0; j < ci.size(); j++)
ci[j]->setVisible(false); 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 #ifndef TEXT_H
#define TEXT_H #define TEXT_H
#include <QGraphicsScene> #include "textitem.h"
class Text : public QGraphicsScene class Text
{ {
public: public:
Text(int size, QObject *parent = 0) : QGraphicsScene(parent) Text(int size) : _sceneRect(QRectF(0, 0, size, size)) {}
{setSceneRect(0, 0, size, size);} ~Text();
void render(QPainter *painter);
void addLabel(const QString &text, const QPointF &pos, const QFont &font, void addLabel(const QString &text, const QPointF &pos, const QFont &font,
const QPen &pen, qreal maxTextWidth); const QPen &pen, qreal maxTextWidth);
void addLabel(const QString &text, const QPainterPath &path, void addLabel(const QString &text, const QPainterPath &path,
const QFont &font, const QPen &pen, qreal maxAngle); 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 #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 #ifndef TEXTITEM_H
#define TEXTITEM_H #define TEXTITEM_H
#include <QGraphicsItem> #include <QPainterPath>
#include <QPen>
#include <QFont>
#include <QString>
class TextItem : public QGraphicsItem #include <QDebug>
class TextItem
{ {
public: public:
TextItem(const QString &text, const QPointF &pos, const QFont &font, TextItem() : _visible(true) {}
int maxTextWidth, QGraphicsItem *parent = 0); virtual ~TextItem() {}
QRectF boundingRect() const {return _boundingRect;} virtual QPainterPath shape() const = 0;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, virtual QRectF boundingRect() const = 0;
QWidget *widget); 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: private:
QString _text; bool _visible;
QRectF _boundingRect;
QFont _font;
QPen _pen;
}; };
#endif // TEXTITEM_H #endif // TEXTITEM_H

View File

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

View File

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