Added support for HiDPI sprites

This commit is contained in:
Martin Tůma 2018-12-04 00:09:23 +01:00
parent 0d868f2f2a
commit 08509f0930
12 changed files with 1779 additions and 39 deletions

View File

@ -3,5 +3,7 @@
<file>style/style.json</file> <file>style/style.json</file>
<file>style/sprite.json</file> <file>style/sprite.json</file>
<file>style/sprite.png</file> <file>style/sprite.png</file>
<file>style/sprite@2x.json</file>
<file>style/sprite@2x.png</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -156,18 +156,20 @@ static void drawFeature(const Feature &feature, Style *style, int styleLayer,
} }
static void drawLayer(const Layer &layer, Style *style, int styleLayer, static void drawLayer(const Layer &layer, Style *style, int styleLayer,
Tile &tile, const QPointF &scale) Tile &tile)
{ {
if (layer.data()->version() > 2) if (layer.data()->version() > 2)
return; return;
QSizeF factor(tile.size().width() / scale.x() / (qreal)layer.data()->extent(), QSizeF factor(tile.size().width() / tile.scale().x() /
tile.size().height() / scale.y() / (qreal)layer.data()->extent()); (qreal)layer.data()->extent(), tile.size().height() / tile.scale().y()
/ (qreal)layer.data()->extent());
tile.painter().save();
style->setupLayer(tile, styleLayer); style->setupLayer(tile, styleLayer);
for (int i = 0; i < layer.features().size(); i++) for (int i = 0; i < layer.features().size(); i++)
drawFeature(layer.features().at(i), style, styleLayer, factor, tile); drawFeature(layer.features().at(i), style, styleLayer, factor, tile);
tile.painter().restore();
} }
bool PBF::render(const QByteArray &data, int zoom, Style *style, bool PBF::render(const QByteArray &data, int zoom, Style *style,
@ -192,8 +194,6 @@ bool PBF::render(const QByteArray &data, int zoom, Style *style,
layers.insert(name, Layer(&layer)); layers.insert(name, Layer(&layer));
} }
t.painter().save();
// Process source layers in order of style layers // Process source layers in order of style layers
for (int i = 0; i < style->sourceLayers().size(); i++) { for (int i = 0; i < style->sourceLayers().size(); i++) {
QMap<QString, Layer>::const_iterator it = layers.find( QMap<QString, Layer>::const_iterator it = layers.find(
@ -201,10 +201,9 @@ bool PBF::render(const QByteArray &data, int zoom, Style *style,
if (it == layers.constEnd()) if (it == layers.constEnd())
continue; continue;
drawLayer(*it, style, i, t, scale); drawLayer(*it, style, i, t);
} }
t.painter().restore();
t.text().render(&t.painter()); t.text().render(&t.painter());
return true; return true;

View File

@ -9,12 +9,19 @@
Loading the sprites atlas image must be deferred until all image plugins Loading the sprites atlas image must be deferred until all image plugins
are loaded, otherwise reading the image will cause a deadlock! are loaded, otherwise reading the image will cause a deadlock!
*/ */
static QImage &atlas(const QString &fileName) static const QImage *atlas(const QString &fileName)
{ {
static QImage *img = new QImage(fileName); static QImage *img = new QImage(fileName);
return *img; return img;
} }
static const QImage *atlas2x(const QString &fileName)
{
static QImage *img = new QImage(fileName);
return img;
}
Sprites::Sprite::Sprite(const QJsonObject &json) Sprites::Sprite::Sprite(const QJsonObject &json)
{ {
int x, y, width, height; int x, y, width, height;
@ -42,7 +49,17 @@ Sprites::Sprite::Sprite(const QJsonObject &json)
bool Sprites::load(const QString &jsonFile, const QString &imageFile) bool Sprites::load(const QString &jsonFile, const QString &imageFile)
{ {
_imageFile = imageFile; _imageFile = imageFile;
return load(jsonFile, _sprites);
}
bool Sprites::load2x(const QString &jsonFile, const QString &imageFile)
{
_image2xFile = imageFile;
return load(jsonFile, _sprites2x);
}
bool Sprites::load(const QString &jsonFile, QMap<QString, Sprite> &map)
{
QFile file(jsonFile); QFile file(jsonFile);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qCritical() << jsonFile << ": error opening file"; qCritical() << jsonFile << ": error opening file";
@ -64,7 +81,7 @@ bool Sprites::load(const QString &jsonFile, const QString &imageFile)
if (val.isObject()) { if (val.isObject()) {
Sprite s(val.toObject()); Sprite s(val.toObject());
if (s.rect().isValid()) if (s.rect().isValid())
_sprites.insert(it.key(), s); map.insert(it.key(), s);
else else
qWarning() << it.key() << ": invalid sprite definition"; qWarning() << it.key() << ": invalid sprite definition";
} else } else
@ -74,20 +91,36 @@ bool Sprites::load(const QString &jsonFile, const QString &imageFile)
return true; return true;
} }
QImage Sprites::icon(const QString &name) const QImage Sprites::icon(const QString &name, bool hidpi) const
{ {
if (_imageFile.isNull()) qreal ratio;
return QImage(); const QImage *img;
const QImage &img = atlas(_imageFile); const QMap<QString, Sprite> *map;
if (img.isNull())
if (hidpi && !_image2xFile.isNull()) {
img = atlas2x(_image2xFile);
map = &_sprites2x;
ratio = 2;
} else if (!_imageFile.isNull()) {
img = atlas(_imageFile);
map = &_sprites;
ratio = 1;
} else
return QImage(); return QImage();
QMap<QString, Sprite>::const_iterator it = _sprites.find(name); if (img->isNull())
if (it == _sprites.constEnd())
return QImage(); return QImage();
if (!img.rect().contains(it->rect()))
QMap<QString, Sprite>::const_iterator it = map->find(name);
if (it == map->constEnd())
return QImage(); return QImage();
return img.copy(it->rect()); if (!img->rect().contains(it->rect()))
return QImage();
QImage ret(img->copy(it->rect()));
ret.setDevicePixelRatio(ratio);
return ret;
} }

View File

@ -9,8 +9,9 @@ class Sprites
{ {
public: public:
bool load(const QString &jsonFile, const QString &imageFile); bool load(const QString &jsonFile, const QString &imageFile);
bool load2x(const QString &jsonFile, const QString &imageFile);
QImage icon(const QString &name) const; QImage icon(const QString &name, bool hidpi) const;
private: private:
class Sprite { class Sprite {
@ -22,8 +23,10 @@ private:
QRect _rect; QRect _rect;
}; };
QMap<QString, Sprite> _sprites; bool load(const QString &jsonFile, QMap<QString, Sprite> &map);
QString _imageFile;
QMap<QString, Sprite> _sprites, _sprites2x;
QString _imageFile, _image2xFile;
}; };
#endif // SPRITES_H #endif // SPRITES_H

View File

@ -223,7 +223,8 @@ QPen Style::Layer::Paint::pen(Type type, int zoom) const
return pen; return pen;
} }
QBrush Style::Layer::Paint::brush(Type type, int zoom, const Sprites &sprites) const QBrush Style::Layer::Paint::brush(Type type, int zoom, const Sprites &sprites,
bool hidpi) const
{ {
QColor color; QColor color;
QBrush brush(Qt::NoBrush); QBrush brush(Qt::NoBrush);
@ -236,7 +237,7 @@ QBrush Style::Layer::Paint::brush(Type type, int zoom, const Sprites &sprites) c
brush = QBrush(color); brush = QBrush(color);
pattern = _fillPattern.value(zoom); pattern = _fillPattern.value(zoom);
if (!pattern.isNull()) if (!pattern.isNull())
brush.setTextureImage(sprites.icon(pattern)); brush.setTextureImage(sprites.icon(pattern, hidpi));
break; break;
case Background: case Background:
color = _backgroundColor.value(zoom); color = _backgroundColor.value(zoom);
@ -244,7 +245,7 @@ QBrush Style::Layer::Paint::brush(Type type, int zoom, const Sprites &sprites) c
brush = QBrush(color); brush = QBrush(color);
pattern = _fillPattern.value(zoom); pattern = _fillPattern.value(zoom);
if (!pattern.isNull()) if (!pattern.isNull())
brush.setTextureImage(sprites.icon(pattern)); brush.setTextureImage(sprites.icon(pattern, hidpi));
break; break;
default: default:
break; break;
@ -448,7 +449,8 @@ void Style::Layer::setPathPainter(Tile &tile, const Sprites &sprites) const
pen.setJoinStyle(_layout.lineJoin(zoom)); pen.setJoinStyle(_layout.lineJoin(zoom));
pen.setCapStyle(_layout.lineCap(zoom)); pen.setCapStyle(_layout.lineCap(zoom));
QBrush brush(_paint.brush(_type, zoom, sprites)); bool hidpi = qMax(tile.scale().x(), tile.scale().y()) > 1.0 ? true : false;
QBrush brush(_paint.brush(_type, zoom, sprites, hidpi));
p.setRenderHint(QPainter::Antialiasing, _paint.antialias(_type, zoom)); p.setRenderHint(QPainter::Antialiasing, _paint.antialias(_type, zoom));
p.setPen(pen); p.setPen(pen);
@ -478,7 +480,8 @@ void Style::Layer::addSymbol(Tile &tile, const QPainterPath &path,
return; return;
QString icon = _layout.icon(tile.zoom(), tags); QString icon = _layout.icon(tile.zoom(), tags);
tile.text().addLabel(text, sprites.icon(icon), path); bool hidpi = qMax(tile.scale().x(), tile.scale().y()) > 1.0 ? true : false;
tile.text().addLabel(text, sprites.icon(icon, hidpi), path);
} }
bool Style::load(const QString &fileName) bool Style::load(const QString &fileName)
@ -509,7 +512,9 @@ bool Style::load(const QString &fileName)
for (int i = 0; i < _layers.size(); i++) for (int i = 0; i < _layers.size(); i++)
_sourceLayers.append(_layers.at(i).sourceLayer()); _sourceLayers.append(_layers.at(i).sourceLayer());
QDir styleDir = QFileInfo(fileName).absoluteDir(); QDir styleDir = QFileInfo(fileName).absoluteDir();
QString spritesJSON(styleDir.filePath("sprite.json")); QString spritesJSON(styleDir.filePath("sprite.json"));
if (QFileInfo::exists(spritesJSON)) { if (QFileInfo::exists(spritesJSON)) {
QString spritesImg(styleDir.filePath("sprite.png")); QString spritesImg(styleDir.filePath("sprite.png"));
@ -519,6 +524,15 @@ bool Style::load(const QString &fileName)
qCritical() << spritesImg << ": no such file"; qCritical() << spritesImg << ": no such file";
} }
QString sprites2xJSON(styleDir.filePath("sprite@2x.json"));
if (QFileInfo::exists(sprites2xJSON)) {
QString sprites2xImg(styleDir.filePath("sprite@2x.png"));
if (QFileInfo::exists(sprites2xImg))
_sprites.load2x(sprites2xJSON, sprites2xImg);
else
qCritical() << sprites2xImg << ": no such file";
}
return true; return true;
} }

View File

@ -136,7 +136,8 @@ private:
Paint(const QJsonObject &json); Paint(const QJsonObject &json);
QPen pen(Layer::Type type, int zoom) const; QPen pen(Layer::Type type, int zoom) const;
QBrush brush(Layer::Type type, int zoom, const Sprites &sprites) const; QBrush brush(Layer::Type type, int zoom, const Sprites &sprites,
bool hidpi) const;
qreal opacity(Layer::Type type, int zoom) const; qreal opacity(Layer::Type type, int zoom) const;
bool antialias(Layer::Type type, int zoom) const; bool antialias(Layer::Type type, int zoom) const;
QString fillPattern(int zoom) const QString fillPattern(int zoom) const

View File

@ -1,4 +1,4 @@
#include <QFontMetrics> #include <QFontMetrics>
#include <QPainter> #include <QPainter>
#include "text.h" #include "text.h"
#include "textpointitem.h" #include "textpointitem.h"

View File

@ -34,8 +34,7 @@ public:
Auto Auto
}; };
Text(const QSize &size) Text(const QSize &size) : _sceneRect(QRectF(QPointF(0, 0), size)) {}
: _sceneRect(QRectF(QPointF(0, 0), size)) {}
~Text(); ~Text();
void setFont(const QFont &font) {_font = font;} void setFont(const QFont &font) {_font = font;}

View File

@ -71,7 +71,8 @@ QRectF TextPointItem::fuzzyBoundingRect(const QString &str,
QRectF TextPointItem::computeTextRect(BoundingRectFunction brf) const QRectF TextPointItem::computeTextRect(BoundingRectFunction brf) const
{ {
QRectF iconRect = _icon.isNull() ? QRectF() : _icon.rect(); QRectF iconRect = _icon.isNull() ? QRectF()
: QRectF(QPointF(0, 0), QSizeF(_icon.size()) / _icon.devicePixelRatioF());
QRectF textRect = brf(text(), font(), _maxWidth); QRectF textRect = brf(text(), font(), _maxWidth);
switch (_anchor) { switch (_anchor) {
@ -108,7 +109,8 @@ TextPointItem::TextPointItem(const QString &text, const QPointF &pos,
_boundingRect = computeTextRect(fuzzyBoundingRect); _boundingRect = computeTextRect(fuzzyBoundingRect);
if (!_icon.isNull()) { if (!_icon.isNull()) {
QRectF iconRect(_icon.rect()); QRectF iconRect(QPointF(0, 0), QSizeF(_icon.size())
/ _icon.devicePixelRatioF());
iconRect.moveCenter(pos); iconRect.moveCenter(pos);
_boundingRect |= iconRect; _boundingRect |= iconRect;
} }
@ -125,8 +127,9 @@ void TextPointItem::paint(QPainter *painter) const
if (!_icon.isNull()) { if (!_icon.isNull()) {
textRect = computeTextRect(exactBoundingRect); textRect = computeTextRect(exactBoundingRect);
painter->drawImage(_pos - QPointF(_icon.width() / 2, painter->drawImage(_pos - QPointF(_icon.width()
_icon.height() / 2), _icon); / _icon.devicePixelRatioF() / 2, _icon.height()
/ _icon.devicePixelRatioF() / 2), _icon);
} else } else
textRect = computeTextRect(fuzzyBoundingRect); textRect = computeTextRect(fuzzyBoundingRect);

View File

@ -8,18 +8,22 @@
class Tile { class Tile {
public: public:
Tile(QImage *img, int zoom, const QPointF &scale) Tile(QImage *img, int zoom, const QPointF &scale)
: _zoom(zoom), _size(img->size()), _text(QSize(img->size().width() : _zoom(zoom), _size(img->size()), _scale(scale),
/ scale.x(), img->size().height() / scale.y())), _painter(img) _text(QSize(img->size().width() / scale.x(),
img->size().height() / scale.y())), _painter(img)
{_painter.scale(scale.x(), scale.y());} {_painter.scale(scale.x(), scale.y());}
int zoom() const {return _zoom;} int zoom() const {return _zoom;}
QSize size() const {return _size;} const QSize &size() const {return _size;}
const QPointF &scale() const {return _scale;}
Text &text() {return _text;} Text &text() {return _text;}
QPainter &painter() {return _painter;} QPainter &painter() {return _painter;}
private: private:
int _zoom; int _zoom;
QSize _size; QSize _size;
QPointF _scale;
Text _text; Text _text;
QPainter _painter; QPainter _painter;
}; };

1682
style/sprite@2x.json Normal file

File diff suppressed because it is too large Load Diff

BIN
style/sprite@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB