From c8b7051eba5554989c488b6145a59b14ccfee687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20T=C5=AFma?= Date: Fri, 14 Jun 2024 18:33:10 +0200 Subject: [PATCH] Added support for SDF sprites + fixed symbol layout algorithms --- src/pbfhandler.h | 4 +- src/sprites.cpp | 88 ++++++++++++++++++++++++++++++++----------- src/sprites.h | 12 +++++- src/style.cpp | 32 ++++++++++------ src/style.h | 24 +++++++----- src/text.cpp | 9 ++--- src/textpointitem.cpp | 2 +- 7 files changed, 117 insertions(+), 54 deletions(-) diff --git a/src/pbfhandler.h b/src/pbfhandler.h index c8ae41e..0bfde9d 100644 --- a/src/pbfhandler.h +++ b/src/pbfhandler.h @@ -11,7 +11,7 @@ class Style; class PBFHandler : public QImageIOHandler { public: - PBFHandler(const Style *style) : _style(style) {} + PBFHandler(Style *style) : _style(style) {} ~PBFHandler() {} bool canRead() const; @@ -24,7 +24,7 @@ public: static bool canRead(QIODevice *device); private: - const Style *_style; + Style *_style; QSize _scaledSize; }; diff --git a/src/sprites.cpp b/src/sprites.cpp index d4bbbd3..d802d0a 100644 --- a/src/sprites.cpp +++ b/src/sprites.cpp @@ -4,22 +4,25 @@ #include #include "sprites.h" - -/* - Loading the sprites atlas image must be deferred until all image plugins - are loaded, otherwise reading the image will cause a deadlock! -*/ -static const QImage &atlas(const QString &fileName) +static void decodeSDF(QImage &img, const QColor &color) { - static QImage img(fileName); - return img; + quint32 argb = color.rgba(); + img.convertTo(QImage::Format_ARGB32_Premultiplied); + uchar *bits = img.bits(); + int bpl = img.bytesPerLine(); + + for (int y = 0; y < img.height(); y++) { + for (int x = 0; x < img.width(); x++) { + quint32 *pixel = (quint32*)(bits + y * bpl + x * 4); + *pixel = ((*pixel >> 24) < 192) ? 0 : argb; + } + } } Sprites::Sprite::Sprite(const QJsonObject &json) { int x, y, width, height; - if (json.contains("x") && json["x"].isDouble()) x = json["x"].toInt(); else @@ -44,12 +47,15 @@ Sprites::Sprite::Sprite(const QJsonObject &json) _pixelRatio = json["pixelRatio"].toDouble(); else _pixelRatio = 1.0; + + if (json.contains("sdf") && json["sdf"].isBool()) + _sdf = json["sdf"].toBool(); + else + _sdf = false; } bool Sprites::load(const QString &jsonFile, const QString &imageFile) { - _imageFile = imageFile; - QFile file(jsonFile); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qCritical() << jsonFile << ": error opening file"; @@ -78,27 +84,63 @@ bool Sprites::load(const QString &jsonFile, const QString &imageFile) qWarning() << it.key() << ": invalid sprite definition"; } + // Loading the sprites atlas image must be deferred until all image plugins + // are loaded, otherwise reading the image will cause a deadlock in Qt! + _imageFile = imageFile; + return true; } -QImage Sprites::icon(const QString &name) const +QImage Sprites::sprite(const Sprite &sprite, const QColor &color, qreal scale) { - if (_imageFile.isEmpty()) + if (!_img.rect().contains(sprite.rect())) return QImage(); - const QImage &img = atlas(_imageFile); - if (img.isNull()) + QImage img(_img.copy(sprite.rect())); + img.setDevicePixelRatio(sprite.pixelRatio()); + + if (sprite.sdf()) { + if (scale != 1.0) { + QSize size(img.size().width() * scale, img.size().height() * scale); + QImage simg(img.scaled(size, Qt::IgnoreAspectRatio, + Qt::SmoothTransformation)); + decodeSDF(simg, color); + return simg; + } else { + decodeSDF(img, color); + return img; + } + } else + return img; +} + +QImage Sprites::icon(const QString &name, const QColor &color, qreal size) +{ + if (name.isNull()) return QImage(); - QMap::const_iterator it = _sprites.find(name); + _lock.lock(); + if (_init <= 0) { + if (_init < 0) { + _lock.unlock(); + return QImage(); + } + + _img = QImage(_imageFile); + if (_img.isNull()) { + qWarning() << _imageFile << ": invalid sprite atlas image"; + _init = -1; + _lock.unlock(); + return QImage(); + } + + _init = 1; + } + _lock.unlock(); + + QMap::const_iterator it = _sprites.constFind(name); if (it == _sprites.constEnd()) return QImage(); - if (!img.rect().contains(it->rect())) - return QImage(); - - QImage ret(img.copy(it->rect())); - ret.setDevicePixelRatio(it->pixelRatio()); - - return ret; + return sprite(*it, color, size); } diff --git a/src/sprites.h b/src/sprites.h index 4fcec57..a9baa77 100644 --- a/src/sprites.h +++ b/src/sprites.h @@ -5,16 +5,19 @@ #include #include #include +#include class QJsonObject; class Sprites { public: + Sprites() : _init(0) {} bool load(const QString &jsonFile, const QString &imageFile); bool isNull() const {return _imageFile.isNull();} - QImage icon(const QString &name) const; + QImage icon(const QString &name, const QColor &color = Qt::black, + qreal size = 1.0); private: class Sprite { @@ -23,14 +26,21 @@ private: const QRect &rect() const {return _rect;} qreal pixelRatio() const {return _pixelRatio;} + bool sdf() const {return _sdf;} private: QRect _rect; qreal _pixelRatio; + bool _sdf; }; + QImage sprite(const Sprite &sprite, const QColor &color, qreal scale); + QMap _sprites; + QImage _img; + QMutex _lock; QString _imageFile; + int _init; }; #endif // SPRITES_H diff --git a/src/style.cpp b/src/style.cpp index 27b3bb4..eee8cdf 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -257,6 +257,9 @@ Style::Layer::Paint::Paint(const QJsonObject &json) _textHaloColor = FunctionC(json["text-halo-color"], QColor()); _textHaloWidth = FunctionF(json["text-halo-width"]); _textHaloBlur = FunctionF(json["text-halo-blur"]); + + // icon + _iconColor = FunctionC(json["icon-color"]); } QPen Style::Layer::Paint::pen(Type type, int zoom) const @@ -292,7 +295,7 @@ QPen Style::Layer::Paint::pen(Type type, int zoom) const return pen; } -QBrush Style::Layer::Paint::brush(Type type, int zoom, const Sprites &sprites) +QBrush Style::Layer::Paint::brush(Type type, int zoom, Sprites &sprites) const { QColor color; @@ -369,6 +372,7 @@ Style::Layer::Layout::Layout(const QJsonObject &json) // icon _icon = Template(FunctionS(json["icon-image"])); + _iconSize = FunctionF(json["icon-size"]); // symbol _symbolPlacement = FunctionS(json["symbol-placement"]); @@ -511,7 +515,7 @@ bool Style::Layer::match(int zoom, const PBF::Feature &feature) const return _filter.match(feature); } -void Style::Layer::setPathPainter(Tile &tile, const Sprites &sprites) const +void Style::Layer::setPathPainter(Tile &tile, Sprites &sprites) const { QPainter &p = tile.painter(); int zoom = tile.zoom(); @@ -544,14 +548,18 @@ void Style::Layer::setTextProperties(Tile &tile) const } void Style::Layer::addSymbol(Tile &tile, const QPainterPath &path, - const PBF::Feature &feature, const Sprites &sprites) const + const PBF::Feature &feature, Sprites &sprites) const { QString text(_layout.text(tile.zoom(), feature)); - if (text.isEmpty()) + QString icon(_layout.icon(tile.zoom(), feature)); + QColor color(_paint.iconColor(tile.zoom())); + qreal size(_layout.iconSize(tile.zoom())); + QImage img(sprites.icon(icon, color, size)); + + if (text.isEmpty() && img.isNull()) return; - QString icon(_layout.icon(tile.zoom(), feature)); - tile.text().addLabel(text, sprites.icon(icon), path); + tile.text().addLabel(text, img, path); } static bool loadSprites(const QDir &styleDir, const QString &json, @@ -604,13 +612,13 @@ bool Style::load(const QString &fileName) return true; } -const Sprites &Style::sprites(const QPointF &scale) const +Sprites &Style::sprites(const QPointF &scale) { return (scale.x() > 1.0 || scale.y() > 1.0) && !_sprites2x.isNull() ? _sprites2x : _sprites; } -void Style::setupLayer(Tile &tile, const Layer &layer) const +void Style::setupLayer(Tile &tile, const Layer &layer) { if (layer.isSymbol()) layer.setTextProperties(tile); @@ -618,7 +626,7 @@ void Style::setupLayer(Tile &tile, const Layer &layer) const layer.setPathPainter(tile, sprites(tile.scale())); } -void Style::drawBackground(Tile &tile) const +void Style::drawBackground(Tile &tile) { QRectF rect(QPointF(0, 0), QSizeF(tile.size().width() / tile.scale().x(), tile.size().height() / tile.scale().y())); @@ -636,7 +644,7 @@ void Style::drawBackground(Tile &tile) const } void Style::drawFeature(const PBF::Feature &feature, const Layer &layer, - Tile &tile, const QSizeF &factor) const + Tile &tile, const QSizeF &factor) { if (!layer.match(tile.zoom(), feature)) return; @@ -652,7 +660,7 @@ void Style::drawFeature(const PBF::Feature &feature, const Layer &layer, } void Style::drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer, - Tile &tile) const + Tile &tile) { if (pbfLayer.data()->version() > 2) return; @@ -671,7 +679,7 @@ void Style::drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer, tile.painter().restore(); } -void Style::render(const PBF &data, Tile &tile) const +void Style::render(const PBF &data, Tile &tile) { drawBackground(tile); diff --git a/src/style.h b/src/style.h index ee125da..7194504 100644 --- a/src/style.h +++ b/src/style.h @@ -29,7 +29,7 @@ public: Style(QObject *parent = 0) : QObject(parent) {} bool load(const QString &fileName); - void render(const PBF &data, Tile &tile) const; + void render(const PBF &data, Tile &tile); private: class Layer { @@ -44,10 +44,10 @@ private: bool isVisible() const {return (_layout.visible());} bool match(int zoom, const PBF::Feature &feature) const; - void setPathPainter(Tile &tile, const Sprites &sprites) const; + void setPathPainter(Tile &tile, Sprites &sprites) const; void setTextProperties(Tile &tile) const; void addSymbol(Tile &tile, const QPainterPath &path, - const PBF::Feature &feature, const Sprites &sprites) const; + const PBF::Feature &feature, Sprites &sprites) const; private: enum Type { @@ -103,6 +103,8 @@ private: {return _text.value(zoom, feature).trimmed();} QString icon(int zoom, const PBF::Feature &feature) const {return _icon.value(zoom, feature);} + qreal iconSize(int zoom) const + {return _iconSize.value(zoom);} QFont font(int zoom) const; Qt::PenCapStyle lineCap(int zoom) const; Qt::PenJoinStyle lineJoin(int zoom) const; @@ -116,6 +118,7 @@ private: Template _text; Template _icon; + FunctionF _iconSize; FunctionF _textSize; FunctionF _textMaxWidth; FunctionF _textMaxAngle; @@ -135,13 +138,15 @@ private: Paint(const QJsonObject &json); QPen pen(Layer::Type type, int zoom) const; - QBrush brush(Layer::Type type, int zoom, const Sprites &sprites) + QBrush brush(Layer::Type type, int zoom, Sprites &sprites) const; qreal opacity(Layer::Type type, int zoom) const; bool antialias(Layer::Type type, int zoom) const; Text::Halo halo(int zoom) const {return Text::Halo(_textHaloColor.value(zoom), _textHaloWidth.value(zoom), _textHaloBlur.value(zoom));} + QColor iconColor(int zoom) const + {return _iconColor.value(zoom);} private: FunctionC _textColor; @@ -150,6 +155,7 @@ private: FunctionC _fillColor; FunctionC _fillOutlineColor; FunctionC _backgroundColor; + FunctionC _iconColor; FunctionF _fillOpacity; FunctionF _lineOpacity; FunctionF _lineWidth; @@ -168,14 +174,14 @@ private: Paint _paint; }; - const Sprites &sprites(const QPointF &scale) const; + Sprites &sprites(const QPointF &scale); - void drawBackground(Tile &tile) const; - void setupLayer(Tile &tile, const Layer &layer) const; + void drawBackground(Tile &tile); + void setupLayer(Tile &tile, const Layer &layer); void drawFeature(const PBF::Feature &feature, const Layer &layer, - Tile &tile, const QSizeF &factor) const; + Tile &tile, const QSizeF &factor); void drawLayer(const PBF::Layer &pbfLayer, const Layer &styleLayer, - Tile &tile) const; + Tile &tile); QVector _layers; Sprites _sprites, _sprites2x; diff --git a/src/text.cpp b/src/text.cpp index a2d7edc..84e2c14 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -14,15 +14,10 @@ Text::~Text() void Text::render(QPainter *painter) const { - QSet set; - for (int i = 0; i < _items.size(); i++) { const TextItem *ti = _items.at(i); - if (ti->isVisible() && _sceneRect.intersects(ti->boundingRect()) - && !set.contains(ti->text())) { + if (ti->isVisible() && _sceneRect.intersects(ti->boundingRect())) ti->paint(painter); - set.insert(ti->text()); - } } } @@ -51,6 +46,8 @@ void Text::addLabel(const QString &text, const QImage &icon, } else { switch (_placement) { case Line: + if (text.isEmpty()) + return; ti = new TextPathItem(text, path, _font, _maxAngle, _sceneRect); break; case LineCenter: diff --git a/src/textpointitem.cpp b/src/textpointitem.cpp index 9463a4f..d018e39 100644 --- a/src/textpointitem.cpp +++ b/src/textpointitem.cpp @@ -85,7 +85,7 @@ TextPointItem::TextPointItem(const QString &text, const QPointF &pos, : TextItem(text, font), _pos(pos), _icon(icon), _maxWidth(maxWidth), _anchor(anchor) { - _textRect = fuzzyBoundingRect(); + _textRect = text.isEmpty() ? QRectF() : fuzzyBoundingRect(); _boundingRect = moveTextRect(_textRect); if (!_icon.isNull()) {