22 Commits
1.4 ... 2.1

Author SHA1 Message Date
d8df5cb595 Added style expressions not supported info 2019-10-22 09:14:06 +02:00
543bf63017 Merge branch 'master' of https://github.com/tumic0/QtPBFImagePlugin 2019-10-06 13:27:10 +02:00
7ea19d260c Fixed broken interpolation of grayscale colors 2019-10-06 13:26:30 +02:00
0185cd904f Added change log link 2019-10-05 22:06:37 +02:00
ec6302d524 Updated scaling sample to fit v2.0 2019-10-05 18:49:34 +02:00
0916d6330c Added tile size info 2019-10-05 18:47:39 +02:00
cc0c16cfd6 Explicitly set the text format (optimization) 2019-10-05 18:38:24 +02:00
8da172c0ff Adjusted icon text alignment 2019-10-05 13:21:36 +02:00
148f79eaef Some more bounding rect computation tweaking 2019-10-04 20:23:34 +02:00
1c42f104d1 Merge branch 'master' of https://github.com/tumic0/QtPBFImagePlugin 2019-10-04 18:57:19 +02:00
536cad2d61 Use QStaticText instead of own text caching 2019-10-04 18:56:45 +02:00
ff1c29e078 Use QStaticText instead of own text caching 2019-10-04 18:54:59 +02:00
e8e9139740 Some more text layout tweeking 2019-10-04 09:26:15 +02:00
a58962ab93 Yet another text rendering improvement/optimization 2019-10-03 22:39:05 +02:00
aacc42c25d Fixed broken down-scaling 2019-10-03 08:38:48 +02:00
3f6b5faa2c Switched to 512x512px tile size 2019-10-03 01:01:40 +02:00
5b6a222999 Added correct maxzoom default value 2019-10-02 23:19:49 +02:00
e00c8feab6 Use the "official" min/max zoom defaults in zoom check 2019-10-02 23:14:20 +02:00
82308857ba Added missing include 2019-10-02 23:12:53 +02:00
044e95e061 Some more text layout fiddeling 2019-10-02 22:57:11 +02:00
f5c15ece52 Improved/optimized text rendering 2019-10-01 22:11:45 +02:00
df4134c03a Alittle bit more sane API... 2019-05-04 12:50:16 +02:00
11 changed files with 131 additions and 113 deletions

View File

@ -16,7 +16,8 @@ placed in the same directory as the style itself. A default fallback style
(OSM-Liberty) for OpenMapTiles is part of the plugin.
"Plain" PBF files as well as gzip compressed files (as used in MBTiles) are
supported by the plugin.
supported by the plugin. The tile size is (since version 2.0 of the plugin) 512px
to fit the styles and available data (OpenMapTiles, Mapbox tiles).
## Usage
Due to a major design flaw in the Mapbox vector tiles specification - the zoom
@ -33,10 +34,10 @@ The plugin supports vector scaling using QImageReader's setScaledSize() method,
so when used like in the following example:
```cpp
QImageReader reader(file, QString::number(zoom).toLatin1());
reader.setScaledSize(QSize(512, 512));
reader.setScaledSize(QSize(1024, 1024));
reader.read(&image);
```
you will get 512x512px tiles with a pixel ratio of 2 (= HiDPI tiles).
you will get 1024x1024px tiles with a pixel ratio of 2 (= HiDPI tiles).
## Build
### Requirements
@ -72,6 +73,10 @@ for most common distros available on OBS.
style are ignored.
* Text PBF features must have a unique id (OpenMapTiles >= v3.7) for the text layout
algorithm to work properly.
* Expressions not supported in the styles, only property functions are implemented.
## Changelog
[Changelog](https://build.opensuse.org/package/view_file/home:tumic:QtPBFImagePlugin/QtPBFImagePlugin/libqt5-qtpbfimageformat.changes)
## Status
A picture is worth a thousand words. Data and styles from https://openmaptiles.org.

View File

@ -40,7 +40,9 @@ static QColor interpolate(const QPair<qreal, QColor> &p0,
qreal p1h, p1s, p1l, p1a;
p1.second.getHslF(&p1h, &p1s, &p1l, &p1a);
return QColor::fromHslF(f(p0h, p1h, ratio), f(p0s, p1s, ratio),
/* Qt returns a hue of -1 for achromatic colors. We convert it to a hue of 1
using qAbs() to enable interpolation of grayscale colors */
return QColor::fromHslF(f(qAbs(p0h), qAbs(p1h), ratio), f(p0s, p1s, ratio),
f(p0l, p1l, ratio), f(p0a, p1a, ratio));
}

View File

@ -8,7 +8,7 @@
#include "pbfhandler.h"
#define TILE_SIZE 256
#define TILE_SIZE 512
#define GZIP_MAGIC 0x1F8B0800
#define GZIP_MAGIC_MASK 0xFFFFFF00
@ -75,8 +75,8 @@ bool PBFHandler::read(QImage *image)
QSize size = _scaledSize.isValid()
? _scaledSize : QSize(TILE_SIZE, TILE_SIZE);
QPointF scale = _scaledSize.isValid()
? QPointF(_scaledSize.width() / TILE_SIZE, _scaledSize.height() / TILE_SIZE)
: QPointF(1.0, 1.0);
? QPointF((qreal)_scaledSize.width() / TILE_SIZE,
(qreal)_scaledSize.height() / TILE_SIZE) : QPointF(1.0, 1.0);
*image = QImage(size, QImage::Format_ARGB32_Premultiplied);
Tile tile(image, ok ? zoom : -1, scale);

View File

@ -437,7 +437,7 @@ Text::RotationAlignment Style::Layer::Layout::textRotationAlignment(int zoom)
}
Style::Layer::Layer(const QJsonObject &json)
: _type(Unknown), _minZoom(-1), _maxZoom(-1)
: _type(Unknown), _minZoom(0), _maxZoom(24)
{
// type
QString type(json["type"].toString());
@ -475,12 +475,8 @@ Style::Layer::Layer(const QJsonObject &json)
bool Style::Layer::match(int zoom, const PBF::Feature &feature) const
{
if (zoom >= 0) {
if (_minZoom > 0 && zoom < _minZoom)
return false;
if (_maxZoom > 0 && zoom > _maxZoom)
return false;
}
if (zoom >= 0 && (zoom < _minZoom || zoom > _maxZoom))
return false;
return _filter.match(feature);
}
@ -601,7 +597,8 @@ void Style::setupLayer(Tile &tile, const Layer &layer) const
void Style::drawBackground(Tile &tile) const
{
QRectF rect(QPointF(0, 0), tile.size());
QRectF rect(QPointF(0, 0), QSizeF(tile.size().width() / tile.scale().x(),
tile.size().height() / tile.scale().y()));
QPainterPath path;
path.addRect(rect);

View File

@ -35,7 +35,7 @@ public:
private:
class Layer {
public:
Layer() : _type(Unknown), _minZoom(-1), _maxZoom(-1) {}
Layer() : _type(Unknown), _minZoom(0), _maxZoom(24) {}
Layer(const QJsonObject &json);
const QString &sourceLayer() const {return _sourceLayer;}

View File

@ -1,6 +1,7 @@
#include <QFontMetrics>
#include <QPainter>
#include <QSet>
#include <QMap>
#include "text.h"
#include "textpointitem.h"
#include "textpathitem.h"
@ -31,22 +32,36 @@ void Text::addLabel(const QString &text, const QImage &icon,
{
TextItem *ti;
switch (_placement) {
case Line:
if (_alignment == Viewport)
if (_alignment == Viewport) {
QMap<qreal, int> map;
for (int j = 0; j < path.elementCount(); j++) {
QLineF l(path.elementAt(j), _sceneRect.center());
map.insert(l.length(), j);
}
QMap<qreal, int>::const_iterator jt = map.constBegin();
ti = new TextPointItem(text, path.elementAt(jt.value()), _font,
_maxWidth, _anchor, icon);
while (true) {
if (_sceneRect.contains(ti->boundingRect()))
break;
if (++jt == map.constEnd())
break;
static_cast<TextPointItem*>(ti)->setPos(path.elementAt(
jt.value()));
}
} else {
switch (_placement) {
case Line:
ti = new TextPathItem(text, path, _font, _maxAngle, _sceneRect);
break;
case LineCenter:
ti = new TextPointItem(text, path.pointAtPercent(0.5), _font,
_maxWidth, _anchor, icon);
break;
default:
ti = new TextPointItem(text, path.elementAt(0), _font,
_maxWidth, _anchor, icon);
else
ti = new TextPathItem(text, path, _font, _maxAngle, _sceneRect);
break;
case LineCenter:
ti = new TextPointItem(text, path.pointAtPercent(0.5), _font,
_maxWidth, _anchor, icon);
break;
default:
ti = new TextPointItem(text, path.elementAt(0), _font, _maxWidth,
_anchor, icon);
break;
}
}
// Note: empty path == point geometry (single move)

View File

@ -11,7 +11,7 @@ bool TextItem::collidesWithItem(const TextItem *other) const
return other->shape().intersects(shape());
}
int TextItem::avgCharWidth() const
qreal TextItem::avgCharWidth() const
{
qreal ratio;
ushort cp = _text.at(0).unicode();
@ -21,14 +21,18 @@ int TextItem::avgCharWidth() const
ratio = 1.0;
// Greek & Cyrilic
else if (cp >= 0x03FF && cp <= 0x04FF) {
ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.75 : 0.67;
ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.80 : 0.73;
if (_font.bold())
ratio *= 1.1;
if (_font.italic())
ratio *= 0.9;
// The rest (Latin scripts, Arabic, ...)
} else {
ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.7 : 0.58;
ratio = (_font.capitalization() == QFont::AllUppercase) ? 0.75 : 0.63;
if (_font.bold())
ratio *= 1.1;
if (_font.italic())
ratio *= 0.9;
}
return ratio * _font.pixelSize();

View File

@ -34,7 +34,7 @@ public:
bool collidesWithItem(const TextItem *other) const;
protected:
int avgCharWidth() const;
qreal avgCharWidth() const;
private:
QString _text;

View File

@ -3,23 +3,23 @@
#include "textpathitem.h"
static QPointF intersection(const QLineF &line, const QRectF &rect)
static bool intersection(const QLineF &line, const QRectF &rect,
QPointF *p)
{
QPointF p;
if (line.intersect(QLineF(rect.topLeft(), rect.topRight()), &p)
if (line.intersect(QLineF(rect.topLeft(), rect.topRight()), p)
== QLineF::BoundedIntersection)
return p;
if (line.intersect(QLineF(rect.topLeft(), rect.bottomLeft()), &p)
return true;
if (line.intersect(QLineF(rect.topLeft(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection)
return p;
if (line.intersect(QLineF(rect.bottomRight(), rect.bottomLeft()), &p)
return true;
if (line.intersect(QLineF(rect.bottomRight(), rect.bottomLeft()), p)
== QLineF::BoundedIntersection)
return p;
if (line.intersect(QLineF(rect.bottomRight(), rect.topRight()), &p)
return true;
if (line.intersect(QLineF(rect.bottomRight(), rect.topRight()), p)
== QLineF::BoundedIntersection)
return p;
return true;
return rect.center();
return false;
}
static QPainterPath subpath(const QList<QLineF> &lines, int start, int end,
@ -63,6 +63,7 @@ static QList<QLineF> lineString(const QPainterPath &path,
{
QList<QLineF> lines;
int start = 0, end = path.elementCount() - 1;
QPointF p;
for (int i = 0; i < path.elementCount(); i++) {
if (boundingRect.contains(path.elementAt(i))) {
@ -79,16 +80,14 @@ static QList<QLineF> lineString(const QPainterPath &path,
if (start > 0) {
QLineF l(path.elementAt(start-1), path.elementAt(start));
QPointF p(intersection(l, boundingRect));
if (p != boundingRect.center())
if (intersection(l, boundingRect, &p))
lines.append(QLineF(p, path.elementAt(start)));
}
for (int i = start + 1; i <= end; i++)
lines.append(QLineF(path.elementAt(i-1), path.elementAt(i)));
if (end < path.elementCount() - 1) {
QLineF l(path.elementAt(end), path.elementAt(end+1));
QPointF p(intersection(l, boundingRect));
if (p != boundingRect.center())
if (intersection(l, boundingRect, &p))
lines.append(QLineF(path.elementAt(end), p));
}
@ -136,7 +135,7 @@ TextPathItem::TextPathItem(const QString &text, const QPainterPath &path,
const QFont &font, int maxAngle, const QRectF &tileRect)
: TextItem(text, font)
{
int cw = avgCharWidth();
qreal cw = avgCharWidth();
int textWidth = text.size() * cw;
if (textWidth > path.length())
return;

View File

@ -1,34 +1,20 @@
#include <QPainter>
#include <QtMath>
#include <QStaticText>
#include "config.h"
#include "textpointitem.h"
#define FLAGS (Qt::AlignCenter | Qt::TextWordWrap | Qt::TextDontClip)
QRectF TextPointItem::exactBoundingRect() const
{
QFontMetrics fm(font());
int limit = font().pixelSize() * _maxWidth;
// 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);
return br;
}
QRectF TextPointItem::fuzzyBoundingRect() const
{
int limit = font().pixelSize() * _maxWidth;
int fs = font().pixelSize();
if (text().size() <= 3)
return QRectF(0, 0, text().size() * fs, fs * 1.6);
int limit = fs * _maxWidth;
qreal cw = avgCharWidth();
qreal lh = font().pixelSize() * 1.25;
int width = 0, lines = 0;
QStringList l(text().split('\n'));
@ -44,7 +30,7 @@ QRectF TextPointItem::fuzzyBoundingRect() const
if (wl + pl < limit) {
pl += wl + cw;
} else {
if (wl > limit) {
if (wl >= limit) {
if (pl > 0)
lines++;
} else
@ -61,11 +47,11 @@ QRectF TextPointItem::fuzzyBoundingRect() const
}
}
return QRectF(0, 0, width, lines * lh);
return QRectF(0, 0, width, lines * fs * 1.6);
}
QRectF TextPointItem::computeTextRect(bool exact) const
QRectF TextPointItem::moveTextRect(const QRectF &rect) const
{
#ifdef ENABLE_HIDPI
QRectF iconRect = _icon.isNull() ? QRectF()
@ -74,19 +60,19 @@ QRectF TextPointItem::computeTextRect(bool exact) const
QRectF iconRect = _icon.isNull() ? QRectF() : QRectF(QPointF(0, 0),
QSizeF(_icon.size()));
#endif // ENABLE_HIDPI
QRectF textRect = exact ? exactBoundingRect() : fuzzyBoundingRect();
QRectF textRect(rect);
switch (_anchor) {
case Text::Center:
textRect.moveCenter(_pos);
break;
case Text::Left:
textRect.moveTopLeft(_pos - QPointF(-iconRect.width() / 2,
textRect.height() / 2));
textRect.moveTopLeft(_pos - QPointF(-iconRect.width() / 2
- font().pixelSize()/4.0, textRect.height() / 2));
break;
case Text::Right:
textRect.moveTopRight(_pos - QPointF(iconRect.width() / 2,
textRect.height() / 2));
textRect.moveTopRight(_pos - QPointF(iconRect.width() / 2
+ font().pixelSize()/4.0, textRect.height() / 2));
break;
case Text::Bottom:
textRect.moveTopLeft(_pos - QPointF(textRect.width() / 2,
@ -106,7 +92,8 @@ TextPointItem::TextPointItem(const QString &text, const QPointF &pos,
: TextItem(text, font), _pos(pos), _icon(icon), _maxWidth(maxWidth),
_anchor(anchor)
{
_boundingRect = computeTextRect(false);
_textRect = fuzzyBoundingRect();
_boundingRect = moveTextRect(_textRect);
if (!_icon.isNull()) {
#ifdef ENABLE_HIDPI
@ -122,17 +109,24 @@ TextPointItem::TextPointItem(const QString &text, const QPointF &pos,
_shape.addRect(_boundingRect);
}
void TextPointItem::setPos(const QPointF &pos)
{
QPointF d(_boundingRect.left() - _pos.x(), _boundingRect.top() - _pos.y());
_boundingRect.moveTopLeft(pos + d);
_shape = QPainterPath();
_shape.addRect(_boundingRect);
_pos = pos;
}
void TextPointItem::paint(QPainter *painter) const
{
//painter->setPen(Qt::red);
//painter->drawRect(_boundingRect);
QRectF textRect;
bool hasHalo = halo().color().isValid() && halo().width() > 0;
painter->setFont(font());
painter->setPen(pen());
if (!_icon.isNull()) {
textRect = (_anchor != Text::Center || hasHalo)
? computeTextRect(true) : _boundingRect;
textRect = moveTextRect(painter->boundingRect(_textRect, FLAGS, text()));
#ifdef ENABLE_HIDPI
painter->drawImage(_pos - QPointF(_icon.width()
/ _icon.devicePixelRatioF() / 2, _icon.height()
@ -142,33 +136,32 @@ void TextPointItem::paint(QPainter *painter) const
_icon.height() / 2), _icon);
#endif // ENABLE_HIDPI
} else
textRect = hasHalo ? computeTextRect(true) : _boundingRect;
textRect = _boundingRect;
if (hasHalo()) {
QStaticText st(text());
st.setTextFormat(Qt::PlainText);
st.setTextWidth(textRect.width());
st.setTextOption(QTextOption(Qt::AlignHCenter));
st.setPerformanceHint(QStaticText::AggressiveCaching);
if (hasHalo) {
QRect ir(textRect.toRect());
QImage img(ir.size(), QImage::Format_ARGB32_Premultiplied);
img.fill(Qt::transparent);
QPainter ip(&img);
ip.setPen(halo().color());
ip.setFont(font());
ip.drawText(img.rect(), FLAGS, text());
painter->drawImage(ir.x() - 1, ir.y() - 1, img);
painter->drawImage(ir.x() + 1, ir.y() + 1, img);
painter->drawImage(ir.x() - 1, ir.y() + 1, img);
painter->drawImage(ir.x() + 1, ir.y() - 1, img);
painter->drawImage(ir.x(), ir.y() - 1, img);
painter->drawImage(ir.x(), ir.y() + 1, img);
painter->drawImage(ir.x() - 1, ir.y(), img);
painter->drawImage(ir.x() + 1, ir.y(), img);
painter->setFont(font());
painter->setPen(pen());
painter->drawText(ir, FLAGS, text());
} else {
painter->setFont(font());
painter->setPen(halo().color());
painter->drawStaticText(textRect.topLeft() + QPointF(-1, -1), st);
painter->drawStaticText(textRect.topLeft() + QPointF(+1, +1), st);
painter->drawStaticText(textRect.topLeft() + QPointF(-1, +1), st);
painter->drawStaticText(textRect.topLeft() + QPointF(+1, -1), st);
painter->drawStaticText(textRect.topLeft() + QPointF(0, -1), st);
painter->drawStaticText(textRect.topLeft() + QPointF(0, +1), st);
painter->drawStaticText(textRect.topLeft() + QPointF(-1, 0), st);
painter->drawStaticText(textRect.topLeft() + QPointF(+1, 0), st);
painter->setPen(pen());
painter->drawStaticText(textRect.topLeft(), st);
} else
painter->drawText(textRect, FLAGS, text());
}
//painter->setBrush(Qt::NoBrush);
//painter->setPen(Qt::red);
//painter->drawRect(_boundingRect);
//painter->setPen(Qt::blue);
//painter->drawRect(textRect);
}

View File

@ -15,14 +15,17 @@ public:
QPainterPath shape() const {return _shape;}
void paint(QPainter *painter) const;
void setPos(const QPointF &pos);
private:
QRectF exactBoundingRect() const;
QRectF fuzzyBoundingRect() const;
QRectF computeTextRect(bool exact) const;
QRectF moveTextRect(const QRectF &rect) const;
bool hasHalo() const
{return halo().color().isValid() && halo().width() > 0;}
QPointF _pos;
QPainterPath _shape;
QRectF _boundingRect;
QRectF _textRect, _boundingRect;
QImage _icon;
int _maxWidth;
Text::Anchor _anchor;