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