1 /*
2  * tileset.cpp
3  * Copyright 2008-2015, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
4  * Copyright 2009, Edward Hutchins <eah1@yahoo.com>
5  *
6  * This file is part of libtiled.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  *    1. Redistributions of source code must retain the above copyright notice,
12  *       this list of conditions and the following disclaimer.
13  *
14  *    2. Redistributions in binary form must reproduce the above copyright
15  *       notice, this list of conditions and the following disclaimer in the
16  *       documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
20  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
21  * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
24  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
27  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include "tileset.h"
31 
32 #include "imagecache.h"
33 #include "tile.h"
34 #include "tilesetmanager.h"
35 #include "wangset.h"
36 
37 #include <QBitmap>
38 
39 #include "qtcompat_p.h"
40 
41 namespace Tiled {
42 
create(const QString & name,int tileWidth,int tileHeight,int tileSpacing,int margin)43 SharedTileset Tileset::create(const QString &name, int tileWidth, int tileHeight, int tileSpacing, int margin)
44 {
45     SharedTileset tileset(new Tileset(name, tileWidth, tileHeight,
46                                       tileSpacing, margin));
47     tileset->mWeakPointer = tileset;
48     TilesetManager::instance()->addTileset(tileset.data());
49     return tileset;
50 }
51 
Tileset(QString name,int tileWidth,int tileHeight,int tileSpacing,int margin)52 Tileset::Tileset(QString name, int tileWidth, int tileHeight,
53                  int tileSpacing, int margin):
54     Object(TilesetType),
55     mName(std::move(name)),
56     mTileWidth(tileWidth),
57     mTileHeight(tileHeight),
58     mTileSpacing(tileSpacing),
59     mMargin(margin),
60     mObjectAlignment(Unspecified),
61     mOrientation(Orthogonal),
62     mGridSize(tileWidth, tileHeight),
63     mColumnCount(0),
64     mExpectedColumnCount(0),
65     mExpectedRowCount(0),
66     mNextTileId(0),
67     mStatus(LoadingReady)
68 {
69     Q_ASSERT(tileSpacing >= 0);
70     Q_ASSERT(margin >= 0);
71 }
72 
~Tileset()73 Tileset::~Tileset()
74 {
75     TilesetManager::instance()->removeTileset(this);
76     qDeleteAll(mTiles);
77     qDeleteAll(mWangSets);
78 }
79 
setFormat(const QString & format)80 void Tileset::setFormat(const QString &format)
81 {
82     mFormat = format;
83 }
84 
format() const85 QString Tileset::format() const
86 {
87     return mFormat;
88 }
89 
90 /**
91  * Sets the tile size of this tileset. Affects how image is cut in loadImage.
92  *
93  * @warning Invalid for image collection tilesets!
94  */
setTileSize(QSize tileSize)95 void Tileset::setTileSize(QSize tileSize)
96 {
97     mTileWidth = tileSize.width();
98     mTileHeight = tileSize.height();
99 }
100 
101 /**
102  * Sets the space in pixels between tiles in the tileset. Affects how image is
103  * cut in loadImage.
104  */
setTileSpacing(int tileSpacing)105 void Tileset::setTileSpacing(int tileSpacing)
106 {
107     Q_ASSERT(tileSpacing >= 0);
108     mTileSpacing = tileSpacing;
109 }
110 
111 /**
112  * Sets the margin used by the tileset image. This is the number of pixels
113  * at the top-left border of the image that is skipped when cutting out tiles.
114  * Affects how image is cut in loadImage.
115  */
setMargin(int margin)116 void Tileset::setMargin(int margin)
117 {
118     Q_ASSERT(margin >= 0);
119     mMargin = margin;
120 }
121 
122 /**
123  * Returns the location of the tile with the given ID.
124  */
findTileLocation(Tile * tile) const125 int Tileset::findTileLocation(Tile *tile) const
126 {
127     return mTiles.indexOf(tile);
128 }
129 
130 /**
131  * Returns the tile with the given ID, creating it when it does not exist yet.
132  */
findOrCreateTile(int id)133 Tile *Tileset::findOrCreateTile(int id)
134 {
135     if (Tile *tile = mTilesById.value(id))
136         return tile;
137 
138     mNextTileId = std::max(mNextTileId, id + 1);
139 
140     auto tile = new Tile(id, this);
141     mTilesById[id] = tile;
142     mTiles.append(tile);
143 
144     return tile;
145 }
146 
147 /**
148  * Returns the number of tile rows in the tileset image.
149  */
rowCount() const150 int Tileset::rowCount() const
151 {
152     if (isCollection())
153         return 1;
154 
155     return rowCountForHeight(mImageReference.size.height());
156 }
157 
158 /**
159  * Sets the transparent color. Pixels with this color will be masked out
160  * when loadFromImage() is called.
161  */
setTransparentColor(const QColor & c)162 void Tileset::setTransparentColor(const QColor &c)
163 {
164     mImageReference.transparentColor = c;
165 }
166 
167 /**
168  * Sets the image reference data for tileset image based tilesets.
169  *
170  * This function also sets the expected column count, which can be used later
171  * for automatic adjustment of tile indexes in case the tileset image width has
172  * changed.
173  */
setImageReference(const ImageReference & reference)174 void Tileset::setImageReference(const ImageReference &reference)
175 {
176     const QUrl oldImageSource = mImageReference.source;
177 
178     mImageReference = reference;
179     mExpectedColumnCount = columnCountForWidth(mImageReference.size.width());
180     mExpectedRowCount = rowCountForHeight(mImageReference.size.height());
181 
182     if (mImageReference.source != oldImageSource)
183         TilesetManager::instance()->tilesetImageSourceChanged(*this, oldImageSource);
184 }
185 
186 /**
187  * Load this tileset from the given tileset \a image. This will replace
188  * existing tile images in this tileset with new ones. If the new image
189  * contains more tiles than exist in the tileset new tiles will be
190  * appended, if there are fewer tiles the excess images will be blanked.
191  *
192  * The tile width and height of this tileset must be higher than 0.
193  *
194  * @param image    the image to load the tiles from
195  * @param source   the url to the image, which will be remembered as the
196  *                 image source of this tileset.
197  * @return <code>true</code> if loading was successful, otherwise
198  *         returns <code>false</code>
199  */
loadFromImage(const QImage & image,const QUrl & source)200 bool Tileset::loadFromImage(const QImage &image, const QUrl &source)
201 {
202     const QUrl oldImageSource = mImageReference.source;
203 
204     mImageReference.source = source;
205 
206     if (mImageReference.source != oldImageSource)
207         TilesetManager::instance()->tilesetImageSourceChanged(*this, oldImageSource);
208 
209     if (image.isNull()) {
210         mImageReference.status = LoadingError;
211         return false;
212     }
213 
214     const QSize tileSize = this->tileSize();
215     if (tileSize.isEmpty())
216         return false;
217 
218     const int margin = this->margin();
219     const int spacing = this->tileSpacing();
220     const int stopWidth = image.width() - tileSize.width();
221     const int stopHeight = image.height() - tileSize.height();
222 
223     int tileNum = 0;
224 
225     for (int y = margin; y <= stopHeight; y += tileSize.height() + spacing) {
226         for (int x = margin; x <= stopWidth; x += tileSize.width() + spacing) {
227             const QImage tileImage = image.copy(x, y, tileSize.width(), tileSize.height());
228             QPixmap tilePixmap = QPixmap::fromImage(tileImage);
229             const QColor &transparent = mImageReference.transparentColor;
230 
231             if (transparent.isValid()) {
232                 const QImage mask = tileImage.createMaskFromColor(transparent.rgb());
233                 tilePixmap.setMask(QBitmap::fromImage(mask));
234             }
235 
236             auto it = mTilesById.find(tileNum);
237             if (it != mTilesById.end()) {
238                 it.value()->setImage(tilePixmap);
239             } else {
240                 auto tile = new Tile(tilePixmap, tileNum, this);
241                 mTilesById.insert(tileNum, tile);
242                 mTiles.insert(tileNum, tile);
243             }
244 
245             ++tileNum;
246         }
247     }
248 
249     // Blank out any remaining tiles to avoid confusion (todo: could be more clear)
250     for (Tile *tile : qAsConst(mTiles)) {
251         if (tile->id() >= tileNum) {
252             QPixmap tilePixmap = QPixmap(tileSize);
253             tilePixmap.fill();
254             tile->setImage(tilePixmap);
255         }
256     }
257 
258     mNextTileId = std::max(mNextTileId, tileNum);
259 
260     mImageReference.size = image.size();
261     mColumnCount = columnCountForWidth(mImageReference.size.width());
262     mImageReference.status = LoadingReady;
263 
264     return true;
265 }
266 
267 /**
268  * Exists only because the Python plugin interface does not handle QUrl (would
269  * be nice to add this). Assumes \a source is a local file when it would
270  * otherwise be a relative URL (without scheme).
271  *
272  * \sa setImageSource
273  */
loadFromImage(const QImage & image,const QString & source)274 bool Tileset::loadFromImage(const QImage &image, const QString &source)
275 {
276     return loadFromImage(image, Tiled::toUrl(source));
277 }
278 
279 /**
280  * Convenience override that loads the image via the ImageCache.
281  */
loadFromImage(const QString & fileName)282 bool Tileset::loadFromImage(const QString &fileName)
283 {
284     const QUrl oldImageSource = mImageReference.source;
285     mImageReference.source = QUrl::fromLocalFile(fileName);
286     if (mImageReference.source != oldImageSource)
287         TilesetManager::instance()->tilesetImageSourceChanged(*this, oldImageSource);
288 
289     return loadImage();
290 }
291 
292 /**
293  * Tries to load the image this tileset is referring to.
294  *
295  * @return <code>true</code> if loading was successful, otherwise
296  *         returns <code>false</code>
297  */
loadImage()298 bool Tileset::loadImage()
299 {
300     TilesheetParameters p;
301     p.fileName = Tiled::urlToLocalFileOrQrc(mImageReference.source);
302     p.tileWidth = mTileWidth;
303     p.tileHeight = mTileHeight;
304     p.spacing = mTileSpacing;
305     p.margin = mMargin;
306     p.transparentColor = mImageReference.transparentColor;
307 
308     if (p.tileWidth <= 0 || p.tileHeight <= 0) {
309         mImageReference.status = LoadingError;
310         return false;
311     }
312 
313     QImage image = ImageCache::loadImage(p.fileName);
314     if (image.isNull()) {
315         mImageReference.status = LoadingError;
316         return false;
317     }
318 
319     auto tiles = ImageCache::cutTiles(p);
320 
321     for (int tileNum = 0; tileNum < tiles.size(); ++tileNum) {
322         auto it = mTilesById.find(tileNum);
323         if (it != mTilesById.end()) {
324             it.value()->setImage(tiles.at(tileNum));
325         } else {
326             auto tile = new Tile(tiles.at(tileNum), tileNum, this);
327             mTilesById.insert(tileNum, tile);
328             mTiles.insert(tileNum, tile);
329         }
330     }
331 
332     QPixmap blank;
333 
334     // Blank out any remaining tiles to avoid confusion (todo: could be more clear)
335     for (Tile *tile : qAsConst(mTiles)) {
336         if (tile->id() >= tiles.size()) {
337             if (blank.isNull()) {
338                 blank = QPixmap(mTileWidth, mTileHeight);
339                 blank.fill();
340             }
341             tile->setImage(blank);
342         }
343     }
344 
345     mNextTileId = std::max<int>(mNextTileId, tiles.size());
346 
347     mImageReference.size = image.size();
348     mColumnCount = columnCountForWidth(mImageReference.size.width());
349     mImageReference.status = LoadingReady;
350 
351     return true;
352 }
353 
354 /**
355  * Returns whether the tiles in \a candidate use the same images as the ones
356  * in \a subject. Note that \a candidate is allowed to have additional tiles
357  * as well.
358  */
sameTileImages(const Tileset & subject,const Tileset & candidate)359 static bool sameTileImages(const Tileset &subject, const Tileset &candidate)
360 {
361     for (const Tile *subjectTile : subject.tiles()) {
362         const Tile *replacementTile = candidate.findTile(subjectTile->id());
363         if (!replacementTile)
364             return false;
365         if (subjectTile->imageSource() != replacementTile->imageSource())
366             return false;
367     }
368 
369     return true;
370 }
371 
372 /**
373  * This checks if there is a similar tileset in the given list.
374  * It is needed for replacing this tileset by its similar copy.
375  */
findSimilarTileset(const QVector<SharedTileset> & tilesets) const376 SharedTileset Tileset::findSimilarTileset(const QVector<SharedTileset> &tilesets) const
377 {
378     // The TilesetManager avoids loading the same external tileset twice, so
379     // for external tilesets we don't need to look for "similar" tilesets.
380     if (isExternal())
381         return SharedTileset();
382 
383     for (const SharedTileset &candidate : tilesets) {
384         Q_ASSERT(candidate != this);
385 
386         if (candidate->tileCount() != tileCount())
387             continue;
388         if (candidate->imageSource() != imageSource())
389             continue;
390         if (candidate->tileSize() != tileSize())
391             continue;
392         if (candidate->tileSpacing() != tileSpacing())
393             continue;
394         if (candidate->margin() != margin())
395             continue;
396         if (candidate->tileOffset() != tileOffset())
397             continue;
398 
399         // For an image collection tileset, check the image sources
400         if (isCollection())
401             if (!sameTileImages(*this, *candidate))
402                 continue;
403 
404         return candidate;
405     }
406 
407     return SharedTileset();
408 }
409 
410 /**
411  * Changes the source of the tileset image.
412  *
413  * Only takes affect when loadImage is called.
414  */
setImageSource(const QUrl & imageSource)415 void Tileset::setImageSource(const QUrl &imageSource)
416 {
417     if (mImageReference.source != imageSource) {
418         const QUrl oldImageSource = mImageReference.source;
419         mImageReference.source = imageSource;
420         TilesetManager::instance()->tilesetImageSourceChanged(*this, oldImageSource);
421     }
422 }
423 
424 /**
425  * Exists only because the Python plugin interface does not handle QUrl (would
426  * be nice to add this). Assumes \a source is a local file when it is either
427  * an absolute file path or would otherwise be a relative URL (without scheme).
428  *
429  * \sa loadFromImage
430  */
setImageSource(const QString & source)431 void Tileset::setImageSource(const QString &source)
432 {
433     setImageSource(Tiled::toUrl(source));
434 }
435 
436 /**
437  * Returns the column count that this tileset would have if the tileset
438  * image would have the given \a width. This takes into account the tile
439  * size, margin and spacing.
440  */
columnCountForWidth(int width) const441 int Tileset::columnCountForWidth(int width) const
442 {
443     if (mTileWidth <= 0)
444         return 0;
445     return (width - mMargin + mTileSpacing) / (mTileWidth + mTileSpacing);
446 }
447 
448 /**
449  * Returns the row count that this tileset would have if the tileset
450  * image would have the given \a width. This takes into account the tile
451  * size, margin and spacing.
452  */
rowCountForHeight(int height) const453 int Tileset::rowCountForHeight(int height) const
454 {
455     if (mTileHeight <= 0)
456         return 0;
457     return (height - mMargin + mTileSpacing) / (mTileHeight + mTileSpacing);
458 }
459 
addWangSet(std::unique_ptr<WangSet> wangSet)460 void Tileset::addWangSet(std::unique_ptr<WangSet> wangSet)
461 {
462     Q_ASSERT(wangSet->tileset() == this);
463     mWangSets.append(wangSet.release());
464 }
465 
466 /**
467  * Adds a wangSet.
468  */
insertWangSet(int index,std::unique_ptr<WangSet> wangSet)469 void Tileset::insertWangSet(int index, std::unique_ptr<WangSet> wangSet)
470 {
471     Q_ASSERT(wangSet->tileset() == this);
472     mWangSets.insert(index, wangSet.release());
473 }
474 
475 /**
476  * @brief Tileset::takeWangSetAt Removes the wangset at a given index
477  *                               And returns it to the caller.
478  * @param index Index to take at.
479  * @return
480  */
takeWangSetAt(int index)481 std::unique_ptr<WangSet> Tileset::takeWangSetAt(int index)
482 {
483     return std::unique_ptr<WangSet>(mWangSets.takeAt(index));
484 }
485 
486 /**
487  * Adds a new tile to the end of the tileset.
488  */
addTile(const QPixmap & image,const QUrl & source)489 Tile *Tileset::addTile(const QPixmap &image, const QUrl &source)
490 {
491     Tile *newTile = new Tile(takeNextTileId(), this);
492     newTile->setImage(image);
493     newTile->setImageSource(source);
494 
495     mTilesById.insert(newTile->id(), newTile);
496     mTiles.append(newTile);
497     if (mTileHeight < image.height())
498         mTileHeight = image.height();
499     if (mTileWidth < image.width())
500         mTileWidth = image.width();
501     return newTile;
502 }
503 
504 /**
505  * Adds the given list of tiles to this tileset.
506  *
507  * The tiles should already have unique tile IDs associated with them!
508  */
addTiles(const QList<Tile * > & tiles)509 void Tileset::addTiles(const QList<Tile *> &tiles)
510 {
511     for (Tile *tile : tiles) {
512         Q_ASSERT(tile->tileset() == this && !mTilesById.contains(tile->id()));
513         mTilesById.insert(tile->id(), tile);
514         mTiles.append(tile);
515     }
516 
517     updateTileSize();
518 }
519 
520 /**
521  * Removes the given list of tiles from this tileset.
522  *
523  * @warning The tiles are not deleted!
524  */
removeTiles(const QList<Tile * > & tiles)525 void Tileset::removeTiles(const QList<Tile *> &tiles)
526 {
527     for (Tile *tile : tiles) {
528         Q_ASSERT(tile->tileset() == this && mTilesById.contains(tile->id()));
529         mTilesById.remove(tile->id());
530         mTiles.removeOne(tile);
531     }
532 
533     updateTileSize();
534 }
535 
536 /**
537  * Removes the given tile from this set and deletes it.
538  */
deleteTile(int id)539 void Tileset::deleteTile(int id)
540 {
541     auto tile = mTilesById.take(id);
542     mTiles.removeOne(tile);
543     delete tile;
544 }
545 
546 /**
547  * Moves the \a tiles from their current position the given \a position.
548  *
549  * Returns the previous locations of the moved tiles.
550  */
relocateTiles(const QList<Tile * > & tiles,int location)551 QList<int> Tileset::relocateTiles(const QList<Tile *> &tiles, int location)
552 {
553     QList<int> prevLocations;
554     for (Tile *tile : tiles) {
555         int fromIndex = mTiles.indexOf(tile);
556         mTiles.move(fromIndex, location);
557         if (fromIndex > location)
558             ++location; // insert the next tile after the previous one
559 
560         prevLocations.append(fromIndex);
561     }
562     return prevLocations;
563 }
564 
anyTileOutOfOrder() const565 bool Tileset::anyTileOutOfOrder() const
566 {
567     int tileId = 0;
568     for (const Tile *tile : mTiles) {
569         if (tile->id() != tileId)
570             return true;
571         ++tileId;
572     }
573     return false;
574 }
575 
576 /**
577  * Sets the \a image to be used for the tile with the given \a id.
578  *
579  * This function makes sure the tile width and tile height properties of the
580  * tileset reflect the maximum size. It is only expected to be used for
581  * image collection tilesets.
582  */
setTileImage(Tile * tile,const QPixmap & image,const QUrl & source)583 void Tileset::setTileImage(Tile *tile,
584                            const QPixmap &image,
585                            const QUrl &source)
586 {
587     Q_ASSERT(isCollection());
588     Q_ASSERT(mTilesById.value(tile->id()) == tile);
589 
590     const QSize previousImageSize = tile->image().size();
591     const QSize newImageSize = image.size();
592 
593     tile->setImage(image);
594     tile->setImageSource(source);
595 
596     if (previousImageSize != newImageSize) {
597         // Update our max. tile size
598         if (previousImageSize.height() == mTileHeight ||
599                 previousImageSize.width() == mTileWidth) {
600             // This used to be the max image; we have to recompute
601             updateTileSize();
602         } else {
603             // Check if we have a new maximum
604             if (mTileHeight < newImageSize.height())
605                 mTileHeight = newImageSize.height();
606             if (mTileWidth < newImageSize.width())
607                 mTileWidth = newImageSize.width();
608         }
609     }
610 }
611 
setOriginalTileset(const SharedTileset & original)612 void Tileset::setOriginalTileset(const SharedTileset &original)
613 {
614     mOriginalTileset = original;
615 }
616 
617 /**
618  * When a tileset gets exported, a copy might be made to apply certain export
619  * options. In this case, the copy will have a (weak) pointer to the original
620  * tileset, to allow issues found during export to refer to this tileset.
621  */
originalTileset() const622 SharedTileset Tileset::originalTileset() const
623 {
624     SharedTileset original { mOriginalTileset };
625     if (!original)
626         original = sharedPointer();
627     return original;
628 }
629 
swap(Tileset & other)630 void Tileset::swap(Tileset &other)
631 {
632     const Properties p = properties();
633     setProperties(other.properties());
634     other.setProperties(p);
635 
636     std::swap(mFileName, other.mFileName);
637     std::swap(mImageReference, other.mImageReference);
638     std::swap(mTileWidth, other.mTileWidth);
639     std::swap(mTileHeight, other.mTileHeight);
640     std::swap(mTileSpacing, other.mTileSpacing);
641     std::swap(mMargin, other.mMargin);
642     std::swap(mTileOffset, other.mTileOffset);
643     std::swap(mObjectAlignment, other.mObjectAlignment);
644     std::swap(mOrientation, other.mOrientation);
645     std::swap(mGridSize, other.mGridSize);
646     std::swap(mColumnCount, other.mColumnCount);
647     std::swap(mExpectedColumnCount, other.mExpectedColumnCount);
648     std::swap(mExpectedRowCount, other.mExpectedRowCount);
649     std::swap(mTilesById, other.mTilesById);
650     std::swap(mTiles, other.mTiles);
651     std::swap(mNextTileId, other.mNextTileId);
652     std::swap(mWangSets, other.mWangSets);
653     std::swap(mStatus, other.mStatus);
654     std::swap(mBackgroundColor, other.mBackgroundColor);
655     std::swap(mFormat, other.mFormat);
656 
657     // Don't swap mWeakPointer, since it's a reference to this.
658 
659     // Update back references from tiles and Wang sets
660     for (auto tile : qAsConst(mTiles))
661         tile->mTileset = this;
662     for (auto wangSet : qAsConst(mWangSets))
663         wangSet->setTileset(this);
664 
665     for (auto tile : qAsConst(other.mTiles))
666         tile->mTileset = &other;
667     for (auto wangSet : qAsConst(other.mWangSets))
668         wangSet->setTileset(&other);
669 }
670 
clone() const671 SharedTileset Tileset::clone() const
672 {
673     SharedTileset c = create(mName, mTileWidth, mTileHeight, mTileSpacing, mMargin);
674     c->setProperties(properties());
675 
676     // mFileName stays empty
677     c->mTileOffset = mTileOffset;
678     c->mObjectAlignment = mObjectAlignment;
679     c->mOrientation = mOrientation;
680     c->mGridSize = mGridSize;
681     c->mColumnCount = mColumnCount;
682     c->mNextTileId = mNextTileId;
683     c->mStatus = mStatus;
684     c->mBackgroundColor = mBackgroundColor;
685     c->mFormat = mFormat;
686     c->mTransformationFlags = mTransformationFlags;
687 
688     for (auto tile : mTiles) {
689         const int id = tile->id();
690         Tile *clonedTile = tile->clone(c.data());
691 
692         c->mTilesById.insert(id, clonedTile);
693         c->mTiles.append(clonedTile);
694     }
695 
696     c->mWangSets.reserve(mWangSets.size());
697     for (WangSet *wangSet : mWangSets)
698         c->mWangSets.append(wangSet->clone(c.data()));
699 
700     // Call setter to please TilesetManager, which starts watching the image of
701     // the tileset when it calls TilesetManager::tilesetImageSourceChanged.
702     c->setImageReference(mImageReference);
703 
704     return c;
705 }
706 
707 /**
708  * Sets tile size to the maximum size.
709  */
updateTileSize()710 void Tileset::updateTileSize()
711 {
712     int maxWidth = 0;
713     int maxHeight = 0;
714     for (Tile *tile : qAsConst(mTiles)) {
715         const QSize size = tile->size();
716         if (maxWidth < size.width())
717             maxWidth = size.width();
718         if (maxHeight < size.height())
719             maxHeight = size.height();
720     }
721     mTileWidth = maxWidth;
722     mTileHeight = maxHeight;
723 }
724 
725 
orientationToString(Tileset::Orientation orientation)726 QString Tileset::orientationToString(Tileset::Orientation orientation)
727 {
728     switch (orientation) {
729     case Tileset::Orthogonal:
730         return QStringLiteral("orthogonal");
731     case Tileset::Isometric:
732         return QStringLiteral("isometric");
733     }
734     return QString();
735 }
736 
orientationFromString(const QString & string)737 Tileset::Orientation Tileset::orientationFromString(const QString &string)
738 {
739     Orientation orientation = Orthogonal;
740     if (string == QLatin1String("isometric"))
741         orientation = Isometric;
742     return orientation;
743 }
744 
745 } // namespace Tiled
746