1 /*
2  * varianttomapconverter.cpp
3  * Copyright 2011, Porfírio José Pereira Ribeiro <porfirioribeiro@gmail.com>
4  * Copyright 2011-2015, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
5  *
6  * This file is part of Tiled.
7  *
8  * This program is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU General Public License as published by the Free
10  * Software Foundation; either version 2 of the License, or (at your option)
11  * any later version.
12  *
13  * This program is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
16  * more details.
17  *
18  * You should have received a copy of the GNU General Public License along with
19  * this program. If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include "varianttomapconverter.h"
23 
24 #include "grouplayer.h"
25 #include "imagelayer.h"
26 #include "map.h"
27 #include "objectgroup.h"
28 #include "objecttemplate.h"
29 #include "properties.h"
30 #include "templatemanager.h"
31 #include "tile.h"
32 #include "tilelayer.h"
33 #include "tileset.h"
34 #include "tilesetmanager.h"
35 #include "wangset.h"
36 
37 #include <memory>
38 
39 namespace Tiled {
40 
resolvePath(const QDir & dir,const QVariant & variant)41 static QString resolvePath(const QDir &dir, const QVariant &variant)
42 {
43     QString fileName = variant.toString();
44     if (!fileName.isEmpty() && QDir::isRelativePath(fileName))
45         return QDir::cleanPath(dir.absoluteFilePath(fileName));
46     return fileName;
47 }
48 
toMap(const QVariant & variant,const QDir & mapDir)49 std::unique_ptr<Map> VariantToMapConverter::toMap(const QVariant &variant,
50                                                   const QDir &mapDir)
51 {
52     mGidMapper.clear();
53     mDir = mapDir;
54 
55     const QVariantMap variantMap = variant.toMap();
56     const QString orientationString = variantMap[QStringLiteral("orientation")].toString();
57 
58     Map::Parameters mapParameters;
59     mapParameters.orientation = orientationFromString(orientationString);
60 
61     if (mapParameters.orientation == Map::Unknown) {
62         mError = tr("Unsupported map orientation: \"%1\"")
63                 .arg(orientationString);
64         return nullptr;
65     }
66 
67     const QString staggerAxis = variantMap[QStringLiteral("staggeraxis")].toString();
68     const QString staggerIndex = variantMap[QStringLiteral("staggerindex")].toString();
69     const QString renderOrder = variantMap[QStringLiteral("renderorder")].toString();
70 
71     const int nextLayerId = variantMap[QStringLiteral("nextlayerid")].toInt();
72     const int nextObjectId = variantMap[QStringLiteral("nextobjectid")].toInt();
73 
74     mapParameters.renderOrder = renderOrderFromString(renderOrder);
75     mapParameters.width = variantMap[QStringLiteral("width")].toInt();
76     mapParameters.height = variantMap[QStringLiteral("height")].toInt();
77     mapParameters.tileWidth = variantMap[QStringLiteral("tilewidth")].toInt();
78     mapParameters.tileHeight = variantMap[QStringLiteral("tileheight")].toInt();
79     mapParameters.infinite = variantMap[QStringLiteral("infinite")].toInt();
80     mapParameters.hexSideLength = variantMap[QStringLiteral("hexsidelength")].toInt();
81     mapParameters.staggerAxis = staggerAxisFromString(staggerAxis);
82     mapParameters.staggerIndex = staggerIndexFromString(staggerIndex);
83 
84     const QString bgColor = variantMap[QStringLiteral("backgroundcolor")].toString();
85     if (QColor::isValidColor(bgColor))
86         mapParameters.backgroundColor = QColor(bgColor);
87 
88     auto map = std::make_unique<Map>(mapParameters);
89     if (nextLayerId)
90         map->setNextLayerId(nextLayerId);
91     if (nextObjectId)
92         map->setNextObjectId(nextObjectId);
93 
94     readMapEditorSettings(*map, variantMap[QStringLiteral("editorsettings")].toMap());
95 
96     mMap = map.get();
97     map->setProperties(extractProperties(variantMap));
98 
99     const auto tilesetVariants = variantMap[QStringLiteral("tilesets")].toList();
100     for (const QVariant &tilesetVariant : tilesetVariants) {
101         SharedTileset tileset = toTileset(tilesetVariant);
102         if (!tileset)
103             return nullptr;
104 
105         map->addTileset(tileset);
106     }
107 
108     const auto layerVariants = variantMap[QStringLiteral("layers")].toList();
109     for (const QVariant &layerVariant : layerVariants) {
110         std::unique_ptr<Layer> layer = toLayer(layerVariant);
111         if (!layer)
112             return nullptr;
113 
114         map->addLayer(std::move(layer));
115     }
116 
117     // Try to load the tileset images
118     auto tilesets = map->tilesets();
119     for (SharedTileset &tileset : tilesets) {
120         if (!tileset->imageSource().isEmpty() && tileset->fileName().isEmpty())
121             tileset->loadImage();
122     }
123 
124     bool ok;
125     const int compressionLevel = variantMap[QStringLiteral("compressionlevel")].toInt(&ok);
126     if (ok)
127         map->setCompressionLevel(compressionLevel);
128 
129     return map;
130 }
131 
toTileset(const QVariant & variant,const QDir & directory)132 SharedTileset VariantToMapConverter::toTileset(const QVariant &variant,
133                                                const QDir &directory)
134 {
135     mDir = directory;
136     mReadingExternalTileset = true;
137 
138     SharedTileset tileset = toTileset(variant);
139     if (tileset && !tileset->imageSource().isEmpty())
140         tileset->loadImage();
141 
142     mReadingExternalTileset = false;
143     return tileset;
144 }
145 
toObjectTemplate(const QVariant & variant,const QDir & directory)146 std::unique_ptr<ObjectTemplate> VariantToMapConverter::toObjectTemplate(const QVariant &variant,
147                                                                         const QDir &directory)
148 {
149     mGidMapper.clear();
150     mDir = directory;
151     return toObjectTemplate(variant);
152 }
153 
toProperties(const QVariant & propertiesVariant,const QVariant & propertyTypesVariant) const154 Properties VariantToMapConverter::toProperties(const QVariant &propertiesVariant,
155                                                const QVariant &propertyTypesVariant) const
156 {
157     Properties properties;
158 
159     // read object-based format (1.0)
160     const QVariantMap propertiesMap = propertiesVariant.toMap();
161     const QVariantMap propertyTypesMap = propertyTypesVariant.toMap();
162     QVariantMap::const_iterator it = propertiesMap.constBegin();
163     QVariantMap::const_iterator it_end = propertiesMap.constEnd();
164     for (; it != it_end; ++it) {
165         int type = nameToType(propertyTypesMap.value(it.key()).toString());
166         if (type == QMetaType::UnknownType)
167             type = QMetaType::QString;
168 
169         const QVariant value = fromExportValue(it.value(), type, mDir);
170         properties[it.key()] = value;
171     }
172 
173     // read array-based format (1.2)
174     const QVariantList propertiesList = propertiesVariant.toList();
175     for (const QVariant &propertyVariant : propertiesList) {
176         const QVariantMap propertyVariantMap = propertyVariant.toMap();
177         const QString propertyName = propertyVariantMap[QStringLiteral("name")].toString();
178         const QString propertyType = propertyVariantMap[QStringLiteral("type")].toString();
179         const QVariant propertyValue = propertyVariantMap[QStringLiteral("value")];
180         int type = nameToType(propertyType);
181         if (type == QMetaType::UnknownType)
182             type = QMetaType::QString;
183         properties[propertyName] = fromExportValue(propertyValue, type, mDir);
184     }
185 
186     return properties;
187 }
188 
toTileset(const QVariant & variant)189 SharedTileset VariantToMapConverter::toTileset(const QVariant &variant)
190 {
191     const QVariantMap variantMap = variant.toMap();
192 
193     const int firstGid = variantMap[QStringLiteral("firstgid")].toInt();
194 
195     // Handle external tilesets
196     const QVariant sourceVariant = variantMap[QStringLiteral("source")];
197     if (!sourceVariant.isNull()) {
198         QString source = resolvePath(mDir, sourceVariant);
199         QString error;
200         SharedTileset tileset = TilesetManager::instance()->loadTileset(source, &error);
201         if (!tileset) {
202             // Insert a placeholder to allow the map to load
203             tileset = Tileset::create(QFileInfo(source).completeBaseName(), 32, 32);
204             tileset->setFileName(source);
205             tileset->setStatus(LoadingError);
206         } else {
207             mGidMapper.insert(firstGid, tileset);
208         }
209         return tileset;
210     }
211 
212     const QString name = variantMap[QStringLiteral("name")].toString();
213     const int tileWidth = variantMap[QStringLiteral("tilewidth")].toInt();
214     const int tileHeight = variantMap[QStringLiteral("tileheight")].toInt();
215     const int spacing = variantMap[QStringLiteral("spacing")].toInt();
216     const int margin = variantMap[QStringLiteral("margin")].toInt();
217     const QVariantMap tileOffset = variantMap[QStringLiteral("tileoffset")].toMap();
218     const QVariantMap grid = variantMap[QStringLiteral("grid")].toMap();
219     const int tileOffsetX = tileOffset[QStringLiteral("x")].toInt();
220     const int tileOffsetY = tileOffset[QStringLiteral("y")].toInt();
221     const int columns = variantMap[QStringLiteral("columns")].toInt();
222     const QString backgroundColor = variantMap[QStringLiteral("backgroundcolor")].toString();
223     const QString objectAlignment = variantMap[QStringLiteral("objectalignment")].toString();
224     const QVariantMap transformations = variantMap[QStringLiteral("transformations")].toMap();
225 
226     if (tileWidth <= 0 || tileHeight <= 0 ||
227             (firstGid == 0 && !mReadingExternalTileset)) {
228         mError = tr("Invalid tileset parameters for tileset '%1'").arg(name);
229         return SharedTileset();
230     }
231 
232     SharedTileset tileset(Tileset::create(name,
233                                           tileWidth, tileHeight,
234                                           spacing, margin));
235 
236     tileset->setObjectAlignment(alignmentFromString(objectAlignment));
237     tileset->setTileOffset(QPoint(tileOffsetX, tileOffsetY));
238     tileset->setColumnCount(columns);
239 
240     if (!transformations.isEmpty()) {
241         Tileset::TransformationFlags transformationFlags;
242 
243         if (transformations[QStringLiteral("hflip")].toBool())
244             transformationFlags |= Tileset::AllowFlipHorizontally;
245         if (transformations[QStringLiteral("vflip")].toBool())
246             transformationFlags |= Tileset::AllowFlipVertically;
247         if (transformations[QStringLiteral("rotate")].toBool())
248             transformationFlags |= Tileset::AllowRotate;
249         if (transformations[QStringLiteral("preferuntransformed")].toBool())
250             transformationFlags |= Tileset::PreferUntransformed;
251 
252         tileset->setTransformationFlags(transformationFlags);
253     }
254 
255     readTilesetEditorSettings(*tileset, variantMap[QStringLiteral("editorsettings")].toMap());
256 
257     if (!grid.isEmpty()) {
258         const QString orientation = grid[QStringLiteral("orientation")].toString();
259         const QSize gridSize(grid[QStringLiteral("width")].toInt(),
260                              grid[QStringLiteral("height")].toInt());
261 
262         tileset->setOrientation(Tileset::orientationFromString(orientation));
263         if (!gridSize.isEmpty())
264             tileset->setGridSize(gridSize);
265     }
266 
267     if (QColor::isValidColor(backgroundColor))
268         tileset->setBackgroundColor(QColor(backgroundColor));
269 
270     QVariant imageVariant = variantMap[QStringLiteral("image")];
271 
272     if (!imageVariant.isNull()) {
273         const int imageWidth = variantMap[QStringLiteral("imagewidth")].toInt();
274         const int imageHeight = variantMap[QStringLiteral("imageheight")].toInt();
275 
276         ImageReference imageRef;
277         imageRef.source = toUrl(imageVariant.toString(), mDir);
278         imageRef.size = QSize(imageWidth, imageHeight);
279 
280         tileset->setImageReference(imageRef);
281     }
282 
283     const QString trans = variantMap[QStringLiteral("transparentcolor")].toString();
284     if (QColor::isValidColor(trans))
285         tileset->setTransparentColor(QColor(trans));
286 
287     tileset->setProperties(extractProperties(variantMap));
288 
289     // Read terrains as a WangSet
290     QVariantList terrainsVariantList = variantMap[QStringLiteral("terrains")].toList();
291     WangSet *terrainWangSet = nullptr;
292     if (!terrainsVariantList.isEmpty()) {
293         auto wangSet = std::make_unique<WangSet>(tileset.data(), tr("Terrains"), WangSet::Corner, -1);
294         wangSet->setColorCount(terrainsVariantList.size());
295 
296         for (int i = 0; i < terrainsVariantList.count(); ++i) {
297             QVariantMap terrainMap = terrainsVariantList[i].toMap();
298 
299             const auto &wc = wangSet->colorAt(i + 1);
300             wc->setName(terrainMap[QStringLiteral("name")].toString());
301             wc->setImageId(terrainMap[QStringLiteral("tile")].toInt());
302             wc->setProperties(extractProperties(terrainMap));
303         }
304 
305         terrainWangSet = wangSet.get();
306         tileset->addWangSet(std::move(wangSet));
307     }
308 
309     // Reads tile information (everything except the properties)
310     auto readTile = [&](Tile *tile, const QVariantMap &tileVar) {
311         bool ok = true;
312 
313         tile->setType(tileVar[QStringLiteral("type")].toString());
314 
315         // Read tile terrain ids as Wang IDs.
316         QList<QVariant> terrains = tileVar[QStringLiteral("terrain")].toList();
317         if (terrains.count() == 4 && terrainWangSet) {
318             WangId wangId;
319             for (int i = 0; i < 4 && ok; ++i) {
320                 const int c = terrains.at(i).toInt(&ok) + 1;
321                 if (ok) {
322                     switch (i) {
323                     case 0: wangId.setIndexColor(WangId::TopLeft, c); break;
324                     case 1: wangId.setIndexColor(WangId::TopRight, c); break;
325                     case 2: wangId.setIndexColor(WangId::BottomLeft, c); break;
326                     case 3: wangId.setIndexColor(WangId::BottomRight, c); break;
327                     }
328                 }
329             }
330 
331             if (terrainWangSet->wangIdIsValid(wangId) && ok)
332                 terrainWangSet->setWangId(tile->id(), wangId);
333         }
334 
335         qreal probability = tileVar[QStringLiteral("probability")].toDouble(&ok);
336         if (ok)
337             tile->setProbability(probability);
338 
339         QVariant imageVariant = tileVar[QStringLiteral("image")];
340         if (!imageVariant.isNull()) {
341             const QUrl imagePath = toUrl(imageVariant.toString(), mDir);
342             tileset->setTileImage(tile, QPixmap(imagePath.toLocalFile()), imagePath);
343         }
344 
345         QVariantMap objectGroupVariant = tileVar[QStringLiteral("objectgroup")].toMap();
346         if (!objectGroupVariant.isEmpty()) {
347             std::unique_ptr<ObjectGroup> objectGroup = toObjectGroup(objectGroupVariant);
348             if (objectGroup) {
349                 objectGroup->setProperties(extractProperties(objectGroupVariant));
350 
351                 // Migrate properties from the object group to the tile. Since
352                 // Tiled 1.1, it is no longer possible to edit the properties
353                 // of this implicit object group, but some users may have set
354                 // them in previous versions.
355                 Properties p = objectGroup->properties();
356                 if (!p.isEmpty()) {
357                     mergeProperties(p, tile->properties());
358                     tile->setProperties(p);
359                     objectGroup->setProperties(Properties());
360                 }
361 
362                 tile->setObjectGroup(std::move(objectGroup));
363             }
364         }
365 
366         QVariantList frameList = tileVar[QStringLiteral("animation")].toList();
367         if (!frameList.isEmpty()) {
368             QVector<Frame> frames(frameList.size());
369             for (int i = frameList.size() - 1; i >= 0; --i) {
370                 const QVariantMap frameVariantMap = frameList[i].toMap();
371                 Frame &frame = frames[i];
372                 frame.tileId = frameVariantMap[QStringLiteral("tileid")].toInt();
373                 frame.duration = frameVariantMap[QStringLiteral("duration")].toInt();
374             }
375             tile->setFrames(frames);
376         }
377     };
378 
379     // Read tiles (1.0 format)
380     const QVariant tilesVariant = variantMap[QStringLiteral("tiles")];
381     const QVariantMap tilesVariantMap = tilesVariant.toMap();
382     QVariantMap::const_iterator it = tilesVariantMap.constBegin();
383     for (; it != tilesVariantMap.end(); ++it) {
384         const int tileId = it.key().toInt();
385         if (tileId < 0) {
386             mError = tr("Invalid (negative) tile id: %1").arg(tileId);
387             return SharedTileset();
388         }
389 
390         Tile *tile = tileset->findOrCreateTile(tileId);
391 
392         const QVariantMap tileVar = it.value().toMap();
393         readTile(tile, tileVar);
394     }
395 
396     // Read tile properties (1.0 format)
397     QVariantMap propertiesVariantMap = variantMap[QStringLiteral("tileproperties")].toMap();
398     QVariantMap propertyTypesVariantMap = variantMap[QStringLiteral("tilepropertytypes")].toMap();
399     for (it = propertiesVariantMap.constBegin(); it != propertiesVariantMap.constEnd(); ++it) {
400         const int tileId = it.key().toInt();
401         const QVariant &propertiesVar = it.value();
402         const QVariant propertyTypesVar = propertyTypesVariantMap.value(it.key());
403         const Properties properties = toProperties(propertiesVar, propertyTypesVar);
404         tileset->findOrCreateTile(tileId)->setProperties(properties);
405     }
406 
407     // Read the tiles saved as a list (1.2 format)
408     const QVariantList tilesVariantList = tilesVariant.toList();
409     for (int i = 0; i < tilesVariantList.count(); ++i) {
410         const QVariantMap tileVar = tilesVariantList[i].toMap();
411         const int tileId  = tileVar[QStringLiteral("id")].toInt();
412         if (tileId < 0) {
413             mError = tr("Invalid (negative) tile id: %1").arg(tileId);
414             return SharedTileset();
415         }
416         Tile *tile = tileset->findOrCreateTile(tileId);
417         readTile(tile, tileVar);
418         tile->setProperties(extractProperties(tileVar));
419     }
420 
421     // Read Wang sets
422     const QVariantList wangSetVariants = variantMap[QStringLiteral("wangsets")].toList();
423     for (const QVariant &wangSetVariant : wangSetVariants) {
424         if (auto wangSet = toWangSet(wangSetVariant.toMap(), tileset.data()))
425             tileset->addWangSet(std::move(wangSet));
426         else
427             return SharedTileset();
428     }
429 
430     if (!mReadingExternalTileset)
431         mGidMapper.insert(firstGid, tileset);
432 
433     return tileset;
434 }
435 
toWangSet(const QVariantMap & variantMap,Tileset * tileset)436 std::unique_ptr<WangSet> VariantToMapConverter::toWangSet(const QVariantMap &variantMap, Tileset *tileset)
437 {
438     const QString name = variantMap[QStringLiteral("name")].toString();
439     const WangSet::Type type = wangSetTypeFromString(variantMap[QStringLiteral("type")].toString());
440     const int tileId = variantMap[QStringLiteral("tile")].toInt();
441 
442     std::unique_ptr<WangSet> wangSet { new WangSet(tileset, name, type, tileId) };
443 
444     wangSet->setProperties(extractProperties(variantMap));
445 
446     const QVariantList colorVariants = variantMap[QStringLiteral("colors")].toList();
447     for (const QVariant &colorVariant : colorVariants)
448         wangSet->addWangColor(toWangColor(colorVariant.toMap()));
449 
450     // For backwards-compatibility
451     QVector<int> cornerColors;
452     QVector<int> edgeColors;
453 
454     const QVariantList edgeColorVariants = variantMap[QStringLiteral("edgecolors")].toList();
455     for (const QVariant &edgeColorVariant : edgeColorVariants) {
456         auto wc = toWangColor(edgeColorVariant.toMap());
457         wangSet->addWangColor(wc);
458         edgeColors.append(wc->colorIndex());
459     }
460     const QVariantList cornerColorVariants = variantMap[QStringLiteral("cornercolors")].toList();
461     for (const QVariant &cornerColorVariant : cornerColorVariants) {
462         auto wc = toWangColor(cornerColorVariant.toMap());
463         wangSet->addWangColor(wc);
464         cornerColors.append(wc->colorIndex());
465     }
466 
467     const QVariantList wangTileVariants = variantMap[QStringLiteral("wangtiles")].toList();
468     for (const QVariant &wangTileVariant : wangTileVariants) {
469         const QVariantMap wangTileVariantMap = wangTileVariant.toMap();
470 
471         const int tileId = wangTileVariantMap[QStringLiteral("tileid")].toInt();
472         const QVariantList wangIdVariant = wangTileVariantMap[QStringLiteral("wangid")].toList();
473 
474         WangId wangId;
475         bool ok = true;
476         for (int i = 0; i < WangId::NumIndexes && ok; ++i)
477             wangId.setIndexColor(i, wangIdVariant[i].toUInt(&ok));
478 
479         // Backwards compatibility with version 1.4:
480         // If the wang set was using explicit corner and edge colors,
481         // map the WangId to the unified colors.
482         if (!cornerColors.isEmpty() || !edgeColors.isEmpty()) {
483             for (int i = 0; i < 4; ++i) {
484                 int color = wangId.cornerColor(i);
485                 if (color > 0 && color <= cornerColors.size())
486                     wangId.setCornerColor(i, cornerColors.at(color - 1));
487             }
488             for (int i = 0; i < 4; ++i) {
489                 int color = wangId.edgeColor(i);
490                 if (color > 0 && color <= edgeColors.size())
491                     wangId.setEdgeColor(i, edgeColors.at(color - 1));
492             }
493         }
494 
495         if (!ok || !wangSet->wangIdIsValid(wangId)) {
496             mError = QStringLiteral("Invalid wangId given for tileId: ") + QString::number(tileId);
497             return nullptr;
498         }
499 
500         wangSet->setWangId(tileId, wangId);
501     }
502 
503 
504     // Do something useful if we loaded an old Wang set
505     if (cornerColors.isEmpty() && !edgeColors.isEmpty())
506         wangSet->setType(WangSet::Edge);
507     if (edgeColors.isEmpty() && !cornerColors.isEmpty())
508         wangSet->setType(WangSet::Corner);
509 
510     return wangSet;
511 }
512 
toWangColor(const QVariantMap & variantMap)513 QSharedPointer<WangColor> VariantToMapConverter::toWangColor(const QVariantMap &variantMap)
514 {
515     const QString name = variantMap[QStringLiteral("name")].toString();
516     const QColor color = variantMap[QStringLiteral("color")].toString();
517     const int imageId = variantMap[QStringLiteral("tile")].toInt();
518     const qreal probability = variantMap[QStringLiteral("probability")].toDouble();
519 
520     auto wangColor = QSharedPointer<WangColor>::create(0,
521                                                        name,
522                                                        color,
523                                                        imageId,
524                                                        probability);
525 
526     wangColor->setProperties(extractProperties(variantMap));
527 
528     return wangColor;
529 }
530 
toObjectTemplate(const QVariant & variant)531 std::unique_ptr<ObjectTemplate> VariantToMapConverter::toObjectTemplate(const QVariant &variant)
532 {
533     const QVariantMap variantMap = variant.toMap();
534 
535     const auto tilesetVariant = variantMap[QStringLiteral("tileset")];
536     const auto objectVariant = variantMap[QStringLiteral("object")];
537 
538     if (!tilesetVariant.isNull())
539         toTileset(tilesetVariant);
540 
541     std::unique_ptr<ObjectTemplate> objectTemplate(new ObjectTemplate);
542     objectTemplate->setObject(toMapObject(objectVariant.toMap()));
543 
544     return objectTemplate;
545 }
546 
toLayer(const QVariant & variant)547 std::unique_ptr<Layer> VariantToMapConverter::toLayer(const QVariant &variant)
548 {
549     const QVariantMap variantMap = variant.toMap();
550     std::unique_ptr<Layer> layer;
551 
552     if (variantMap[QStringLiteral("type")] == QLatin1String("tilelayer"))
553         layer = toTileLayer(variantMap);
554     else if (variantMap[QStringLiteral("type")] == QLatin1String("objectgroup"))
555         layer = toObjectGroup(variantMap);
556     else if (variantMap[QStringLiteral("type")] == QLatin1String("imagelayer"))
557         layer = toImageLayer(variantMap);
558     else if (variantMap[QStringLiteral("type")] == QLatin1String("group"))
559         layer = toGroupLayer(variantMap);
560 
561     if (layer) {
562         layer->setId(variantMap[QStringLiteral("id")].toInt());
563         layer->setOpacity(variantMap[QStringLiteral("opacity")].toReal());
564         layer->setVisible(variantMap[QStringLiteral("visible")].toBool());
565         layer->setTintColor(variantMap[QStringLiteral("tintcolor")].value<QColor>());
566         layer->setProperties(extractProperties(variantMap));
567 
568         const QPointF offset(variantMap[QStringLiteral("offsetx")].toDouble(),
569                              variantMap[QStringLiteral("offsety")].toDouble());
570         layer->setOffset(offset);
571 
572         bool ok;
573         QPointF parallaxFactor(1.0, 1.0);
574         const qreal factorX = variantMap[QStringLiteral("parallaxx")].toDouble(&ok);
575         if (ok)
576             parallaxFactor.setX(factorX);
577         const qreal factorY = variantMap[QStringLiteral("parallaxy")].toDouble(&ok);
578         if (ok)
579             parallaxFactor.setY(factorY);
580 
581         layer->setParallaxFactor(parallaxFactor);
582     }
583 
584     return layer;
585 }
586 
toTileLayer(const QVariantMap & variantMap)587 std::unique_ptr<TileLayer> VariantToMapConverter::toTileLayer(const QVariantMap &variantMap)
588 {
589     const QString name = variantMap[QStringLiteral("name")].toString();
590     const int width = variantMap[QStringLiteral("width")].toInt();
591     const int height = variantMap[QStringLiteral("height")].toInt();
592     const int startX = variantMap[QStringLiteral("startx")].toInt();
593     const int startY = variantMap[QStringLiteral("starty")].toInt();
594     const QVariant dataVariant = variantMap[QStringLiteral("data")];
595 
596     typedef std::unique_ptr<TileLayer> TileLayerPtr;
597     TileLayerPtr tileLayer(new TileLayer(name,
598                                          variantMap[QStringLiteral("x")].toInt(),
599                                          variantMap[QStringLiteral("y")].toInt(),
600                                          width, height));
601 
602     const QString encoding = variantMap[QStringLiteral("encoding")].toString();
603     const QString compression = variantMap[QStringLiteral("compression")].toString();
604 
605     Map::LayerDataFormat layerDataFormat;
606     if (encoding.isEmpty() || encoding == QLatin1String("csv")) {
607         layerDataFormat = Map::CSV;
608     } else if (encoding == QLatin1String("base64")) {
609         if (compression.isEmpty()) {
610             layerDataFormat = Map::Base64;
611         } else if (compression == QLatin1String("gzip")) {
612             layerDataFormat = Map::Base64Gzip;
613         } else if (compression == QLatin1String("zlib")) {
614             layerDataFormat = Map::Base64Zlib;
615         } else if (compression == QLatin1String("zstd")) {
616             layerDataFormat = Map::Base64Zstandard;
617         } else {
618             mError = tr("Compression method '%1' not supported").arg(compression);
619             return nullptr;
620         }
621     } else {
622         mError = tr("Unknown encoding: %1").arg(encoding);
623         return nullptr;
624     }
625     mMap->setLayerDataFormat(layerDataFormat);
626 
627     if (dataVariant.isValid() && !dataVariant.isNull()) {
628         if (!readTileLayerData(*tileLayer, dataVariant, layerDataFormat,
629                                QRect(startX, startY, tileLayer->width(), tileLayer->height()))) {
630             return nullptr;
631         }
632     } else {
633         const QVariantList chunks = variantMap[QStringLiteral("chunks")].toList();
634         for (const QVariant &chunkVariant : chunks) {
635             const QVariantMap chunkVariantMap = chunkVariant.toMap();
636             const QVariant chunkData = chunkVariantMap[QStringLiteral("data")];
637             int x = chunkVariantMap[QStringLiteral("x")].toInt();
638             int y = chunkVariantMap[QStringLiteral("y")].toInt();
639             int width = chunkVariantMap[QStringLiteral("width")].toInt();
640             int height = chunkVariantMap[QStringLiteral("height")].toInt();
641 
642             readTileLayerData(*tileLayer, chunkData, layerDataFormat, QRect(x, y, width, height));
643         }
644     }
645 
646     return tileLayer;
647 }
648 
toObjectGroup(const QVariantMap & variantMap)649 std::unique_ptr<ObjectGroup> VariantToMapConverter::toObjectGroup(const QVariantMap &variantMap)
650 {
651     typedef std::unique_ptr<ObjectGroup> ObjectGroupPtr;
652     ObjectGroupPtr objectGroup(new ObjectGroup(variantMap[QStringLiteral("name")].toString(),
653                                                variantMap[QStringLiteral("x")].toInt(),
654                                                variantMap[QStringLiteral("y")].toInt()));
655 
656     objectGroup->setColor(variantMap.value(QLatin1String("color")).value<QColor>());
657 
658     const QString drawOrderString = variantMap.value(QLatin1String("draworder")).toString();
659     if (!drawOrderString.isEmpty()) {
660         objectGroup->setDrawOrder(drawOrderFromString(drawOrderString));
661         if (objectGroup->drawOrder() == ObjectGroup::UnknownOrder) {
662             mError = tr("Invalid draw order: %1").arg(drawOrderString);
663             return nullptr;
664         }
665     }
666 
667     const auto objectVariants = variantMap[QStringLiteral("objects")].toList();
668     for (const QVariant &objectVariant : objectVariants)
669         objectGroup->addObject(toMapObject(objectVariant.toMap()));
670 
671     return objectGroup;
672 }
673 
toMapObject(const QVariantMap & variantMap)674 std::unique_ptr<MapObject> VariantToMapConverter::toMapObject(const QVariantMap &variantMap)
675 {
676     const QString name = variantMap[QStringLiteral("name")].toString();
677     const QString type = variantMap[QStringLiteral("type")].toString();
678     const int id = variantMap[QStringLiteral("id")].toInt();
679     const int gid = variantMap[QStringLiteral("gid")].toInt();
680     const QVariant templateVariant = variantMap[QStringLiteral("template")];
681     const qreal x = variantMap[QStringLiteral("x")].toReal();
682     const qreal y = variantMap[QStringLiteral("y")].toReal();
683     const qreal width = variantMap[QStringLiteral("width")].toReal();
684     const qreal height = variantMap[QStringLiteral("height")].toReal();
685     const qreal rotation = variantMap[QStringLiteral("rotation")].toReal();
686 
687     const QPointF pos(x, y);
688     const QSizeF size(width, height);
689 
690     auto object = std::make_unique<MapObject>(name, type, pos, size);
691     object->setId(id);
692 
693     if (variantMap.contains(QLatin1String("rotation"))) {
694         object->setRotation(rotation);
695         object->setPropertyChanged(MapObject::RotationProperty);
696     }
697 
698     if (!templateVariant.isNull()) { // This object is a template instance
699         QString templateFileName = resolvePath(mDir, templateVariant);
700         auto objectTemplate = TemplateManager::instance()->loadObjectTemplate(templateFileName);
701         object->setObjectTemplate(objectTemplate);
702     }
703 
704     object->setId(id);
705 
706     object->setPropertyChanged(MapObject::NameProperty, !name.isEmpty());
707     object->setPropertyChanged(MapObject::TypeProperty, !type.isEmpty());
708     object->setPropertyChanged(MapObject::SizeProperty, !size.isEmpty());
709 
710     if (gid) {
711         bool ok;
712         object->setCell(mGidMapper.gidToCell(gid, ok));
713 
714         if (const Tile *tile = object->cell().tile()) {
715             const QSizeF &tileSize = tile->size();
716             if (width == 0)
717                 object->setWidth(tileSize.width());
718             if (height == 0)
719                 object->setHeight(tileSize.height());
720         }
721 
722         object->setPropertyChanged(MapObject::CellProperty);
723     }
724 
725     if (variantMap.contains(QLatin1String("visible"))) {
726         object->setVisible(variantMap[QStringLiteral("visible")].toBool());
727         object->setPropertyChanged(MapObject::VisibleProperty);
728     }
729 
730     object->setProperties(extractProperties(variantMap));
731 
732     const QVariant polylineVariant = variantMap[QStringLiteral("polyline")];
733     const QVariant polygonVariant = variantMap[QStringLiteral("polygon")];
734     const QVariant ellipseVariant = variantMap[QStringLiteral("ellipse")];
735     const QVariant pointVariant = variantMap[QStringLiteral("point")];
736     const QVariant textVariant = variantMap[QStringLiteral("text")];
737 
738     if (polygonVariant.userType() == QVariant::List) {
739         object->setShape(MapObject::Polygon);
740         object->setPolygon(toPolygon(polygonVariant));
741         object->setPropertyChanged(MapObject::ShapeProperty);
742     }
743     if (polylineVariant.userType() == QVariant::List) {
744         object->setShape(MapObject::Polyline);
745         object->setPolygon(toPolygon(polylineVariant));
746         object->setPropertyChanged(MapObject::ShapeProperty);
747     }
748     if (ellipseVariant.toBool()) {
749         object->setShape(MapObject::Ellipse);
750         object->setPropertyChanged(MapObject::ShapeProperty);
751     }
752     if (pointVariant.toBool()) {
753         object->setShape(MapObject::Point);
754         object->setPropertyChanged(MapObject::ShapeProperty);
755     }
756     if (textVariant.userType() == QVariant::Map) {
757         object->setTextData(toTextData(textVariant.toMap()));
758         object->setShape(MapObject::Text);
759         object->setPropertyChanged(MapObject::TextProperty);
760     }
761 
762     object->syncWithTemplate();
763 
764     return object;
765 }
766 
toImageLayer(const QVariantMap & variantMap)767 std::unique_ptr<ImageLayer> VariantToMapConverter::toImageLayer(const QVariantMap &variantMap)
768 {
769     typedef std::unique_ptr<ImageLayer> ImageLayerPtr;
770     ImageLayerPtr imageLayer(new ImageLayer(variantMap[QStringLiteral("name")].toString(),
771                                             variantMap[QStringLiteral("x")].toInt(),
772                                             variantMap[QStringLiteral("y")].toInt()));
773 
774     const QString trans = variantMap[QStringLiteral("transparentcolor")].toString();
775     if (QColor::isValidColor(trans))
776         imageLayer->setTransparentColor(QColor(trans));
777 
778     QVariant imageVariant = variantMap[QStringLiteral("image")].toString();
779 
780     if (!imageVariant.isNull()) {
781         const QUrl imageSource = toUrl(imageVariant.toString(), mDir);
782         imageLayer->loadFromImage(imageSource);
783     }
784 
785     return imageLayer;
786 }
787 
toGroupLayer(const QVariantMap & variantMap)788 std::unique_ptr<GroupLayer> VariantToMapConverter::toGroupLayer(const QVariantMap &variantMap)
789 {
790     const QString name = variantMap[QStringLiteral("name")].toString();
791     const int x = variantMap[QStringLiteral("x")].toInt();
792     const int y = variantMap[QStringLiteral("y")].toInt();
793 
794     auto groupLayer = std::make_unique<GroupLayer>(name, x, y);
795 
796     const auto layerVariants = variantMap[QStringLiteral("layers")].toList();
797     for (const QVariant &layerVariant : layerVariants) {
798         std::unique_ptr<Layer> layer = toLayer(layerVariant);
799         if (!layer)
800             return nullptr;
801 
802         groupLayer->addLayer(std::move(layer));
803     }
804 
805     return groupLayer;
806 }
807 
toPolygon(const QVariant & variant) const808 QPolygonF VariantToMapConverter::toPolygon(const QVariant &variant) const
809 {
810     QPolygonF polygon;
811     const auto pointVariants = variant.toList();
812     for (const QVariant &pointVariant : pointVariants) {
813         const QVariantMap pointVariantMap = pointVariant.toMap();
814         const qreal pointX = pointVariantMap[QStringLiteral("x")].toReal();
815         const qreal pointY = pointVariantMap[QStringLiteral("y")].toReal();
816         polygon.append(QPointF(pointX, pointY));
817     }
818     return polygon;
819 }
820 
toTextData(const QVariantMap & variant) const821 TextData VariantToMapConverter::toTextData(const QVariantMap &variant) const
822 {
823     TextData textData;
824 
825     const QString family = variant[QStringLiteral("fontfamily")].toString();
826     const int pixelSize = variant[QStringLiteral("pixelsize")].toInt();
827 
828     if (!family.isEmpty())
829         textData.font.setFamily(family);
830     if (pixelSize > 0)
831         textData.font.setPixelSize(pixelSize);
832 
833     textData.wordWrap = variant[QStringLiteral("wrap")].toInt() == 1;
834     textData.font.setBold(variant[QStringLiteral("bold")].toInt() == 1);
835     textData.font.setItalic(variant[QStringLiteral("italic")].toInt() == 1);
836     textData.font.setUnderline(variant[QStringLiteral("underline")].toInt() == 1);
837     textData.font.setStrikeOut(variant[QStringLiteral("strikeout")].toInt() == 1);
838     if (variant.contains(QLatin1String("kerning")))
839         textData.font.setKerning(variant[QStringLiteral("kerning")].toInt() == 1);
840 
841     QString colorString = variant[QStringLiteral("color")].toString();
842     if (!colorString.isEmpty())
843         textData.color = QColor(colorString);
844 
845     Qt::Alignment alignment;
846 
847     QString hAlignString = variant[QStringLiteral("halign")].toString();
848     if (hAlignString == QLatin1String("center"))
849         alignment |= Qt::AlignHCenter;
850     else if (hAlignString == QLatin1String("right"))
851         alignment |= Qt::AlignRight;
852     else if (hAlignString == QLatin1String("justify"))
853         alignment |= Qt::AlignJustify;
854     else
855         alignment |= Qt::AlignLeft;
856 
857     QString vAlignString = variant[QStringLiteral("valign")].toString();
858     if (vAlignString == QLatin1String("center"))
859         alignment |= Qt::AlignVCenter;
860     else if (vAlignString == QLatin1String("bottom"))
861         alignment |= Qt::AlignBottom;
862     else
863         alignment |= Qt::AlignTop;
864 
865     textData.alignment = alignment;
866 
867     textData.text = variant[QStringLiteral("text")].toString();
868 
869     return textData;
870 }
871 
readMapEditorSettings(Map & map,const QVariantMap & editorSettings)872 void VariantToMapConverter::readMapEditorSettings(Map &map, const QVariantMap &editorSettings)
873 {
874     const QVariantMap chunkSizeVariant = editorSettings[QStringLiteral("chunksize")].toMap();
875     int chunkWidth = chunkSizeVariant[QStringLiteral("width")].toInt();
876     int chunkHeight = chunkSizeVariant[QStringLiteral("height")].toInt();
877     chunkWidth = chunkWidth == 0 ? CHUNK_SIZE : qMax(CHUNK_SIZE_MIN, chunkWidth);
878     chunkHeight = chunkHeight == 0 ? CHUNK_SIZE : qMax(CHUNK_SIZE_MIN, chunkHeight);
879     map.setChunkSize(QSize(chunkWidth, chunkHeight));
880 
881     const QVariantMap exportVariant = editorSettings[QStringLiteral("export")].toMap();
882     const QString target = exportVariant[QStringLiteral("target")].toString();
883     if (!target.isEmpty() && target != QLatin1String("."))
884         map.exportFileName = QDir::cleanPath(mDir.filePath(target));
885     map.exportFormat = exportVariant[QStringLiteral("format")].toString();
886 }
887 
readTilesetEditorSettings(Tileset & tileset,const QVariantMap & editorSettings)888 void VariantToMapConverter::readTilesetEditorSettings(Tileset &tileset, const QVariantMap &editorSettings)
889 {
890     const QVariantMap exportVariant = editorSettings[QStringLiteral("export")].toMap();
891     const QString target = exportVariant[QStringLiteral("target")].toString();
892     if (!target.isEmpty() && target != QLatin1String("."))
893         tileset.exportFileName = QDir::cleanPath(mDir.filePath(target));
894     tileset.exportFormat = exportVariant[QStringLiteral("format")].toString();
895 }
896 
readTileLayerData(TileLayer & tileLayer,const QVariant & dataVariant,Map::LayerDataFormat layerDataFormat,QRect bounds)897 bool VariantToMapConverter::readTileLayerData(TileLayer &tileLayer,
898                                               const QVariant &dataVariant,
899                                               Map::LayerDataFormat layerDataFormat,
900                                               QRect bounds)
901 {
902     switch (layerDataFormat) {
903     case Map::XML:
904     case Map::CSV: {
905         const QVariantList dataVariantList = dataVariant.toList();
906 
907         if (dataVariantList.size() != bounds.width() * bounds.height()) {
908             mError = tr("Corrupt layer data for layer '%1'").arg(tileLayer.name());
909             return false;
910         }
911 
912         int x = bounds.x();
913         int y = bounds.y();
914         bool ok;
915 
916         for (const QVariant &gidVariant : dataVariantList) {
917             const unsigned gid = gidVariant.toUInt(&ok);
918             if (!ok) {
919                 mError = tr("Unable to parse tile at (%1,%2) on layer '%3'")
920                         .arg(x).arg(y).arg(tileLayer.name());
921                 return false;
922             }
923 
924             const Cell cell = mGidMapper.gidToCell(gid, ok);
925 
926             tileLayer.setCell(x, y, cell);
927 
928             x++;
929             if (x > bounds.right()) {
930                 x = bounds.x();
931                 y++;
932             }
933         }
934         break;
935     }
936 
937     case Map::Base64:
938     case Map::Base64Zlib:
939     case Map::Base64Gzip:
940     case Map::Base64Zstandard:{
941         const QByteArray data = dataVariant.toByteArray();
942         GidMapper::DecodeError error = mGidMapper.decodeLayerData(tileLayer,
943                                                                   data,
944                                                                   layerDataFormat,
945                                                                   bounds);
946 
947         switch (error) {
948         case GidMapper::CorruptLayerData:
949             mError = tr("Corrupt layer data for layer '%1'").arg(tileLayer.name());
950             return false;
951         case GidMapper::TileButNoTilesets:
952             mError = tr("Tile used but no tilesets specified");
953             return false;
954         case GidMapper::InvalidTile:
955             mError = tr("Invalid tile: %1").arg(mGidMapper.invalidTile());
956             return false;
957         case GidMapper::NoError:
958             break;
959         }
960 
961         break;
962     }
963     }
964 
965     return true;
966 }
967 
extractProperties(const QVariantMap & variantMap) const968 Properties VariantToMapConverter::extractProperties(const QVariantMap &variantMap) const
969 {
970     return toProperties(variantMap[QStringLiteral("properties")],
971                         variantMap[QStringLiteral("propertytypes")]);
972 }
973 
974 } // namespace Tiled
975