From 6cd122f59bccb9654a8aedd01db05ca960637afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20T=C5=AFma?= Date: Wed, 5 Apr 2023 19:28:17 +0200 Subject: [PATCH] Fix/improve Mapsforge tiles rendering --- src/map/mapsforge/mapdata.cpp | 56 +++++----- src/map/mapsforge/mapdata.h | 27 +++-- src/map/mapsforge/rastertile.cpp | 185 +++++++++++++------------------ src/map/mapsforge/rastertile.h | 50 ++++++--- src/map/mapsforge/style.cpp | 4 +- src/map/mapsforge/style.h | 2 +- src/map/mapsforge/subfile.h | 1 + src/map/mapsforgemap.cpp | 2 +- 8 files changed, 162 insertions(+), 165 deletions(-) diff --git a/src/map/mapsforge/mapdata.cpp b/src/map/mapsforge/mapdata.cpp index 15402070..c4cbe35b 100644 --- a/src/map/mapsforge/mapdata.cpp +++ b/src/map/mapsforge/mapdata.cpp @@ -3,6 +3,7 @@ #include #include #include +#include "common/hash.h" #include "map/osm.h" #include "subfile.h" #include "mapdata.h" @@ -14,7 +15,7 @@ using namespace Mapsforge; #define MD(val) ((val) / 1e6) #define OFFSET_MASK 0x7FFFFFFFFFL -static uint pointType(const QVector &tags) +static quint8 pointType(const QVector &tags) { for (int i = 0; i < tags.size(); i++) { const MapData::Tag &tag = tags.at(i); @@ -37,20 +38,19 @@ static uint pointType(const QVector &tags) static void setPointId(MapData::Point &p) { - uint hash = (uint)qHash(QPair((uint)qHash( - QPair(p.coordinates.lon(), p.coordinates.lat())), - (uint)qHash(p.label))); - uint type = pointType(p.tags); + HASH_T hash = qHash(QPair(p.coordinates.lon(), + p.coordinates.lat())); + quint8 type = pointType(p.tags); - p.id = ((quint64)type)<<32 | hash; + p.id = ((quint64)type)<<56 | (quint64)hash; } static void copyPaths(const RectC &rect, const QList *src, - QList *dst) + QSet *dst) { for (int i = 0; i < src->size(); i++) if (rect.intersects(src->at(i).poly.boundingRect())) - dst->append(src->at(i)); + dst->insert(src->at(i)); } static void copyPoints(const RectC &rect, const QList *src, @@ -194,7 +194,7 @@ static bool readDoubleDelta(SubFile &subfile, const Coordinates &c, return true; } -static bool readPolygon(SubFile &subfile, const Coordinates &c, +static bool readPolygonPath(SubFile &subfile, const Coordinates &c, bool doubleDelta, Polygon &polygon) { quint32 blocks, nodes; @@ -202,7 +202,7 @@ static bool readPolygon(SubFile &subfile, const Coordinates &c, if (!subfile.readVUInt32(blocks)) return false; - polygon.reserve(blocks); + polygon.reserve(polygon.size() + blocks); for (quint32 i = 0; i < blocks; i++) { if (!subfile.readVUInt32(nodes) || !nodes) return false; @@ -494,7 +494,7 @@ void MapData::clearTiles() bool MapData::pathCb(VectorTile *tile, void *context) { PathCTX *ctx = (PathCTX*)context; - ctx->data->paths(tile, ctx->rect, ctx->zoom, ctx->list); + ctx->data->paths(tile, ctx->rect, ctx->zoom, ctx->set); return true; } @@ -545,10 +545,10 @@ void MapData::points(const VectorTile *tile, const RectC &rect, int zoom, copyPoints(rect, cached, list); } -void MapData::paths(const RectC &rect, int zoom, QList *list) +void MapData::paths(const RectC &rect, int zoom, QSet *set) { int l(level(zoom)); - PathCTX ctx(this, rect, zoom, list); + PathCTX ctx(this, rect, zoom, set); double min[2], max[2]; min[0] = rect.left(); @@ -560,7 +560,7 @@ void MapData::paths(const RectC &rect, int zoom, QList *list) } void MapData::paths(const VectorTile *tile, const RectC &rect, int zoom, - QList *list) + QSet *set) { Key key(tile, zoom); QList *cached = _pathCache.object(key); @@ -568,12 +568,12 @@ void MapData::paths(const VectorTile *tile, const RectC &rect, int zoom, if (!cached) { QList *p = new QList(); if (readPaths(tile, zoom, p)) { - copyPaths(rect, p, list); + copyPaths(rect, p, set); _pathCache.insert(key, p); } else delete p; } else - copyPaths(rect, cached, list); + copyPaths(rect, cached, set); } bool MapData::readPaths(const VectorTile *tile, int zoom, QList *list) @@ -604,7 +604,7 @@ bool MapData::readPaths(const VectorTile *tile, int zoom, QList *list) return false; for (unsigned i = 0; i < paths[zoom - info.min]; i++) { - Path p; + Path p(subfile.offset() + subfile.pos()); qint32 lon = 0, lat = 0; if (!(subfile.readVUInt32(unused) && subfile.readUInt16(bitmap) @@ -646,15 +646,15 @@ bool MapData::readPaths(const VectorTile *tile, int zoom, QList *list) Q_ASSERT(blocks); for (unsigned j = 0; j < blocks; j++) { - if (!readPolygon(subfile, tile->pos, flags & 0x04, p.poly)) + if (!readPolygonPath(subfile, tile->pos, flags & 0x04, p.poly)) return false; - p.closed = isClosed(p.poly); - if (flags & 0x10) - p.labelPos = Coordinates(p.poly.first().first().lon() + MD(lon), - p.poly.first().first().lat() + MD(lat)); - - list->append(p); } + p.closed = isClosed(p.poly); + if (flags & 0x10) + p.labelPos = Coordinates(p.poly.first().first().lon() + MD(lon), + p.poly.first().first().lat() + MD(lat)); + + list->append(p); } return true; @@ -720,6 +720,7 @@ bool MapData::readPoints(const VectorTile *tile, int zoom, QList *list) } setPointId(p); + list->append(p); } @@ -735,15 +736,14 @@ QDebug operator<<(QDebug dbg, const Mapsforge::MapData::Tag &tag) QDebug operator<<(QDebug dbg, const MapData::Path &path) { - dbg.nospace() << "Path(" << path.poly.boundingRect() << ", " << path.label - << ", " << path.tags << ")"; + dbg.nospace() << "Path(" << path.poly.boundingRect() << ", " + << path.tags << ")"; return dbg.space(); } QDebug operator<<(QDebug dbg, const MapData::Point &point) { - dbg.nospace() << "Point(" << point.coordinates << "," << point.label - << ", " << point.tags << ")"; + dbg.nospace() << "Point(" << point.coordinates << ", " << point.tags << ")"; return dbg.space(); } #endif // QT_NO_DEBUG diff --git a/src/map/mapsforge/mapdata.h b/src/map/mapsforge/mapdata.h index aabd71e1..a774dc32 100644 --- a/src/map/mapsforge/mapdata.h +++ b/src/map/mapsforge/mapdata.h @@ -44,29 +44,29 @@ public: struct Point { Point(const Coordinates &c) : coordinates(c) {} + quint64 id; Coordinates coordinates; QVector tags; int layer; - quint64 id; - - QString label; bool operator<(const Point &other) const {return id > other.id;} }; struct Path { + Path(quint64 id) : id(id) {} + + quint64 id; Polygon poly; QVector tags; Coordinates labelPos; int layer; bool closed; - QString label; - QPainterPath path; - bool operator<(const Path &other) const {return layer < other.layer;} + bool operator==(const Path &other) const + {return (id == other.id);} }; RectC bounds() const; @@ -75,7 +75,7 @@ public: int tileSize() const {return _tileSize;} void points(const RectC &rect, int zoom, QList *list); - void paths(const RectC &rect, int zoom, QList *list); + void paths(const RectC &rect, int zoom, QSet *set); void load(); void clear(); @@ -101,13 +101,13 @@ private: }; struct PathCTX { - PathCTX(MapData *data, const RectC &rect, int zoom, QList *list) - : data(data), rect(rect), zoom(zoom), list(list) {} + PathCTX(MapData *data, const RectC &rect, int zoom, QSet *set) + : data(data), rect(rect), zoom(zoom), set(set) {} MapData *data; const RectC ▭ int zoom; - QList *list; + QSet *set; }; struct PointCTX { @@ -140,7 +140,7 @@ private: int level(int zoom) const; void paths(const VectorTile *tile, const RectC &rect, int zoom, - QList *list); + QSet *set); void points(const VectorTile *tile, const RectC &rect, int zoom, QList *list); bool readPaths(const VectorTile *tile, int zoom, QList *list); @@ -175,6 +175,11 @@ inline HASH_T qHash(const MapData::Tag &tag) return ::qHash(tag.key) ^ ::qHash(tag.value); } +inline HASH_T qHash(const MapData::Path &path) +{ + return ::qHash(path.id); +} + } #ifndef QT_NO_DEBUG diff --git a/src/map/mapsforge/rastertile.cpp b/src/map/mapsforge/rastertile.cpp index 81519701..6994e46f 100644 --- a/src/map/mapsforge/rastertile.cpp +++ b/src/map/mapsforge/rastertile.cpp @@ -3,7 +3,6 @@ #include "common/programpaths.h" #include "map/mapsforgemap.h" #include "map/textpathitem.h" -#include "map/textpointitem.h" #include "rastertile.h" using namespace Mapsforge; @@ -44,34 +43,19 @@ static QPointF centroid(const QPainterPath &polygon) return QPointF(cx * factor, cy * factor); } -static QString *pointLabel(const Style::TextRender *ri, MapData::Point &point) +static const QByteArray *label(const QByteArray &key, + const QVector &tags, bool *limit = 0) { - for (int i = 0; i < point.tags.size(); i++) { - if (point.tags.at(i).key == ri->key()) { - if (point.tags.at(i).value.isEmpty()) + for (int i = 0; i < tags.size(); i++) { + const MapData::Tag &tag = tags.at(i); + + if (tag.key == key) { + if (tag.value.isEmpty()) return 0; else { - point.label = point.tags.at(i).value; - return &point.label; - } - } - } - - return 0; -} - -static QString *pathLabel(const Style::TextRender *ri, MapData::Path &path, - bool *limit = 0) -{ - for (int i = 0; i < path.tags.size(); i++) { - if (path.tags.at(i).key == ri->key()) { - if (path.tags.at(i).value.isEmpty()) - return 0; - else { - path.label = path.tags.at(i).value; if (limit) - *limit = (path.tags.at(i).key == "ref"); - return &path.label; + *limit = (tag.key == "ref"); + return &tag.value; } } } @@ -94,14 +78,14 @@ void RasterTile::processPointLabels(QList &textItems) for (int i = 0; i < _points.size(); i++) { MapData::Point &point = _points[i]; - QString *label = 0; + const QByteArray *l = 0; const Style::TextRender *ti = 0; const Style::Symbol *si = 0; for (int j = 0; j < labels.size(); j++) { const Style::TextRender *ri = labels.at(j); if (ri->rule().match(point.tags)) { - if ((label = pointLabel(ri, point))) { + if ((l = label(ri->key(), point.tags))) { ti = ri; break; } @@ -124,9 +108,8 @@ void RasterTile::processPointLabels(QList &textItems) const QColor *color = ti ? &ti->fillColor() : 0; const QColor *hColor = ti ? haloColor(ti) : 0; - TextPointItem *item = new TextPointItem( - ll2xy(point.coordinates).toPoint(), label, font, img, color, - hColor, 0); + PointItem *item = new PointItem(ll2xy(point.coordinates).toPoint(), + l ? new QString(*l) : 0, font, img, color, hColor); if (item->isValid() && !item->collides(textItems)) textItems.append(item); else @@ -134,34 +117,36 @@ void RasterTile::processPointLabels(QList &textItems) } } -void RasterTile::processAreaLabels(QList &textItems) +void RasterTile::processAreaLabels(QList &textItems, + QVector &renderPaths) { const Style &s = style(_ratio); QList labels(s.areaLabels(_zoom)); QList symbols(s.areaSymbols(_zoom)); - for (int i = 0; i < _paths.size(); i++) { - MapData::Path &path = _paths[i]; - QString *label = 0; + for (int i = 0; i < renderPaths.size(); i++) { + RenderPath &path = renderPaths[i]; const Style::TextRender *ti = 0; const Style::Symbol *si = 0; - if (!path.closed) + if (!path.path->closed) continue; for (int j = 0; j < labels.size(); j++) { const Style::TextRender *ri = labels.at(j); - if (ri->rule().match(path.closed, path.tags)) { - if ((label = pathLabel(ri, path))) { + if (ri->rule().match(path.path->closed, path.path->tags)) { + const QByteArray *l; + if ((l = label(ri->key(), path.path->tags))) { + path.label = *l; ti = ri; - break; } + break; } } for (int j = 0; j < symbols.size(); j++) { const Style::Symbol *ri = symbols.at(j); - if (ri->rule().match(path.tags)) { + if (ri->rule().match(path.path->tags)) { si = ri; break; } @@ -170,18 +155,15 @@ void RasterTile::processAreaLabels(QList &textItems) if (!ti && !si) continue; - if (!path.path.elementCount()) - path.path = painterPath(path.poly, false); - const QImage *img = si ? &si->img() : 0; const QFont *font = ti ? &ti->font() : 0; const QColor *color = ti ? &ti->fillColor() : 0; const QColor *hColor = ti ? haloColor(ti) : 0; - QPointF pos = path.labelPos.isNull() - ? centroid(path.path) : ll2xy(path.labelPos); + QPointF pos = path.path->labelPos.isNull() + ? centroid(path.pp) : ll2xy(path.path->labelPos); - TextPointItem *item = new TextPointItem(pos.toPoint(), label, font, img, - color, hColor, 0); + TextPointItem *item = new TextPointItem(pos.toPoint(), &path.label, + font, img, color, hColor, 0); if (item->isValid() && _rect.contains(item->boundingRect().toRect()) && !item->collides(textItems)) textItems.append(item); @@ -190,38 +172,40 @@ void RasterTile::processAreaLabels(QList &textItems) } } -void RasterTile::processLineLabels(QList &textItems) +void RasterTile::processLineLabels(QList &textItems, + QVector &renderPaths) { const Style &s = style(_ratio); QList instructions(s.pathLabels(_zoom)); - QSet set; + QSet set; + bool limit; for (int i = 0; i < instructions.size(); i++) { const Style::TextRender *ri = instructions.at(i); - for (int j = 0; j < _paths.size(); j++) { - MapData::Path &path = _paths[j]; - QString *label = 0; - bool limit = false; + for (int i = 0; i < renderPaths.size(); i++) { + RenderPath &path = renderPaths[i]; + const QByteArray *l = label(ri->key(), path.path->tags, + &limit); - if (!path.path.elementCount()) + if (!l) continue; - if (!ri->rule().match(path.closed, path.tags)) + if (!ri->rule().match(path.path->closed, path.path->tags)) continue; - if (!(label = pathLabel(ri, path, &limit))) - continue; - if (limit && set.contains(path.label)) + if (limit && set.contains(*l)) continue; - TextPathItem *item = new TextPathItem(path.path, label, _rect, + path.label = *l; + + TextPathItem *item = new TextPathItem(path.pp, &path.label, _rect, &ri->font(), &ri->fillColor(), haloColor(ri)); if (item->isValid() && !item->collides(textItems)) { textItems.append(item); if (limit) - set.insert(path.label); + set.insert(*l); } else delete item; - } + } } } @@ -262,28 +246,35 @@ QPainterPath RasterTile::painterPath(const Polygon &polygon, bool curve) const return path; } -QVector RasterTile::pathInstructions() +QVector RasterTile::pathInstructions( + QVector &renderPaths) { - QCache > cache(8192); + QCache > cache(8192); QVector instructions; const Style &s = style(_ratio); - QVector *ri; - - for (int i = 0 ; i < _paths.size(); i++) { - MapData::Path &path = _paths[i]; + QList *ri; + int i = 0; + for (QSet::const_iterator it = _paths.cbegin(); + it != _paths.cend(); ++it) { + const MapData::Path &path = *it; + RenderPath &rp = renderPaths[i]; Key key(_zoom, path.closed, path.tags); - QVector *cached = cache.object(key); - if (!cached) { - ri = new QVector(s.paths(_zoom, + + rp.path = &path; + + if (!(ri = cache.object(key))) { + ri = new QList(s.paths(_zoom, path.closed, path.tags)); for (int j = 0; j < ri->size(); j++) - instructions.append(PathInstruction(ri->at(j), &path)); + instructions.append(PathInstruction(ri->at(j), &rp)); cache.insert(key, ri); } else { - for (int j = 0; j < cached->size(); j++) - instructions.append(PathInstruction(cached->at(j), &path)); + for (int j = 0; j < ri->size(); j++) + instructions.append(PathInstruction(ri->at(j), &rp)); } + + i++; } std::sort(instructions.begin(), instructions.end()); @@ -291,47 +282,22 @@ QVector RasterTile::pathInstructions() return instructions; } -void RasterTile::drawPaths(QPainter *painter) +void RasterTile::drawPaths(QPainter *painter, QVector &renderPaths) { - QVector instructions(pathInstructions()); - const Style::PathRender *lri = 0; - - QPixmap layer(_pixmap.size()); - layer.setDevicePixelRatio(_ratio); - layer.fill(Qt::transparent); - - QPainter lp(&layer); - lp.setRenderHint(QPainter::Antialiasing); - lp.translate(-_rect.x(), -_rect.y()); - lp.setCompositionMode(QPainter::CompositionMode_Source); + QVector instructions(pathInstructions(renderPaths)); for (int i = 0; i < instructions.size(); i++) { - PathInstruction &is = instructions[i]; + const PathInstruction &is = instructions.at(i); const Style::PathRender *ri = is.render(); + RenderPath *path = is.path(); - if (lri && lri != ri) { - painter->drawPixmap(_rect.topLeft(), layer); - lp.fillRect(QRect(_rect.topLeft(), _pixmap.size()), Qt::transparent); - } + if (!path->pp.elementCount()) + path->pp = painterPath(path->path->poly, ri->curve()); - if (!is.path()->path.elementCount()) - is.path()->path = painterPath(is.path()->poly, ri->curve()); - - if (ri->area()) { - lp.setPen(ri->pen(_zoom)); - lp.setBrush(ri->brush()); - lp.drawPath(is.path()->path); - lri = ri; - } else { - painter->setPen(ri->pen(_zoom)); - painter->setBrush(ri->brush()); - painter->drawPath(is.path()->path); - lri = 0; - } + painter->setPen(ri->pen(_zoom)); + painter->setBrush(ri->brush()); + painter->drawPath(path->pp); } - - if (lri) - painter->drawPixmap(_rect.topLeft(), layer); } void RasterTile::render() @@ -339,6 +305,7 @@ void RasterTile::render() std::sort(_points.begin(), _points.end()); QList textItems; + QVector renderPaths(_paths.size()); _pixmap.setDevicePixelRatio(_ratio); _pixmap.fill(Qt::transparent); @@ -347,11 +314,11 @@ void RasterTile::render() painter.setRenderHint(QPainter::Antialiasing); painter.translate(-_rect.x(), -_rect.y()); - drawPaths(&painter); + drawPaths(&painter, renderPaths); processPointLabels(textItems); - processAreaLabels(textItems); - processLineLabels(textItems); + processAreaLabels(textItems, renderPaths); + processLineLabels(textItems, renderPaths); drawTextItems(&painter, textItems); //painter.setPen(Qt::red); diff --git a/src/map/mapsforge/rastertile.h b/src/map/mapsforge/rastertile.h index 0cdb97d0..cdbe4de8 100644 --- a/src/map/mapsforge/rastertile.h +++ b/src/map/mapsforge/rastertile.h @@ -4,6 +4,7 @@ #include #include "map/projection.h" #include "map/transform.h" +#include "map/textpointitem.h" #include "style.h" #include "mapdata.h" @@ -16,7 +17,7 @@ class RasterTile { public: RasterTile(const Projection &proj, const Transform &transform, int zoom, - const QRect &rect, qreal ratio, const QList &paths, + const QRect &rect, qreal ratio, const QSet &paths, const QList &points) : _proj(proj), _transform(transform), _zoom(zoom), _rect(rect), _ratio(ratio), _pixmap(rect.width() * ratio, rect.height() * ratio), _paths(paths), @@ -30,27 +31,35 @@ public: void render(); private: + struct RenderPath { + RenderPath() : path(0) {} + + QPainterPath pp; + QString label; + const MapData::Path *path; + }; + class PathInstruction { public: PathInstruction() : _render(0), _path(0) {} - PathInstruction(const Style::PathRender *render, MapData::Path *path) + PathInstruction(const Style::PathRender *render, RenderPath *path) : _render(render), _path(path) {} bool operator<(const PathInstruction &other) const { - if (_path->layer == other._path->layer) + if (_path->path->layer == other._path->path->layer) return _render->zOrder() < other._render->zOrder(); else - return (_path->layer < other._path->layer); + return (_path->path->layer < other._path->path->layer); } const Style::PathRender *render() const {return _render;} - MapData::Path *path() {return _path;} + RenderPath *path() const {return _path;} private: const Style::PathRender *_render; - MapData::Path *_path; + RenderPath *_path; }; struct Key { @@ -67,18 +76,32 @@ private: const QVector &tags; }; - friend HASH_T qHash(const RasterTile::Key &key); - friend HASH_T qHash(const RasterTile::PathInstruction &pi); + class PointItem : public TextPointItem + { + public: + PointItem(const QPoint &point, QString *text, const QFont *font, + const QImage *img, const QColor *color, const QColor *haloColor) + : TextPointItem(point, text, font, img, color, haloColor, 0), + _label(text) {} + ~PointItem() {delete _label;} - QVector pathInstructions(); + private: + QString *_label; + }; + + friend HASH_T qHash(const RasterTile::Key &key); + + QVector pathInstructions(QVector &renderPaths); QPointF ll2xy(const Coordinates &c) const {return _transform.proj2img(_proj.ll2xy(c));} void processPointLabels(QList &textItems); - void processAreaLabels(QList &textItems); - void processLineLabels(QList &textItems); + void processAreaLabels(QList &textItems, + QVector &renderPaths); + void processLineLabels(QList &textItems, + QVector &renderPaths); QPainterPath painterPath(const Polygon &polygon, bool curve) const; void drawTextItems(QPainter *painter, const QList &textItems); - void drawPaths(QPainter *painter); + void drawPaths(QPainter *painter, QVector &renderPaths); Projection _proj; Transform _transform; @@ -86,8 +109,9 @@ private: QRect _rect; qreal _ratio; QPixmap _pixmap; - QList _paths; + QSet _paths; QList _points; + bool _valid; }; diff --git a/src/map/mapsforge/style.cpp b/src/map/mapsforge/style.cpp index f2a4f340..559a4f6c 100644 --- a/src/map/mapsforge/style.cpp +++ b/src/map/mapsforge/style.cpp @@ -432,10 +432,10 @@ Style::Style(const QString &path, qreal ratio) loadXml(":/mapsforge/default.xml", ratio); } -QVector Style::paths(int zoom, bool closed, +QList Style::paths(int zoom, bool closed, const QVector &tags) const { - QVector ri; + QList ri; for (int i = 0; i < _paths.size(); i++) if (_paths.at(i).rule().match(zoom, closed, tags)) diff --git a/src/map/mapsforge/style.h b/src/map/mapsforge/style.h index 392dfe2b..92f59d7c 100644 --- a/src/map/mapsforge/style.h +++ b/src/map/mapsforge/style.h @@ -221,7 +221,7 @@ public: Style(const QString &path, qreal ratio); - QVector paths(int zoom, bool closed, + QList paths(int zoom, bool closed, const QVector &tags) const; QList pathLabels(int zoom) const; QList pointLabels(int zoom) const; diff --git a/src/map/mapsforge/subfile.h b/src/map/mapsforge/subfile.h index 956d2b31..948ebd53 100644 --- a/src/map/mapsforge/subfile.h +++ b/src/map/mapsforge/subfile.h @@ -14,6 +14,7 @@ public: : _file(file), _offset(offset), _size(size), _pos(-1), _blockNum(-1), _blockPos(-1) {} + quint64 offset() const {return _offset;} quint64 pos() const {return _pos;} bool seek(quint64 pos); diff --git a/src/map/mapsforgemap.cpp b/src/map/mapsforgemap.cpp index 463b2e8e..1c98b197 100644 --- a/src/map/mapsforgemap.cpp +++ b/src/map/mapsforgemap.cpp @@ -175,7 +175,7 @@ void MapsforgeMap::draw(QPainter *painter, const QRectF &rect, Flags flags) if (QPixmapCache::find(key(_zoom, ttl), &pm)) painter->drawPixmap(ttl, pm); else { - QList paths; + QSet paths; QList points; /* Add a "sub-pixel" margin to assure the tile areas do not