1 /*
2  * gidmapper.cpp
3  * Copyright 2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
4  *
5  * This file is part of libtiled.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are met:
9  *
10  *    1. Redistributions of source code must retain the above copyright notice,
11  *       this list of conditions and the following disclaimer.
12  *
13  *    2. Redistributions in binary form must reproduce the above copyright
14  *       notice, this list of conditions and the following disclaimer in the
15  *       documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
20  * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
23  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
25  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
26  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include "gidmapper.h"
30 
31 #include "compression.h"
32 #include "tile.h"
33 #include "tiled.h"
34 #include "tileset.h"
35 
36 #include <algorithm>
37 
38 using namespace Tiled;
39 
40 // Bits on the far end of the 32-bit global tile ID are used for tile flags
41 const unsigned FlippedHorizontallyFlag   = 0x80000000;
42 const unsigned FlippedVerticallyFlag     = 0x40000000;
43 const unsigned FlippedAntiDiagonallyFlag = 0x20000000;
44 
45 const unsigned RotatedHexagonal120Flag   = 0x10000000;
46 
47 /**
48  * Default constructor. Use \l insert to initialize the gid mapper
49  * incrementally.
50  */
GidMapper()51 GidMapper::GidMapper()
52 {
53 }
54 
55 /**
56  * Constructor that initializes the gid mapper using the given \a tilesets.
57  */
GidMapper(const QVector<SharedTileset> & tilesets)58 GidMapper::GidMapper(const QVector<SharedTileset> &tilesets)
59     : GidMapper()
60 {
61     unsigned firstGid = 1;
62     for (const SharedTileset &tileset : tilesets) {
63         insert(firstGid, tileset);
64         firstGid += tileset->nextTileId();
65     }
66 }
67 
68 /**
69  * Returns the cell data matched by the given \a gid. The \a ok parameter
70  * indicates whether an error occurred.
71  */
gidToCell(unsigned gid,bool & ok) const72 Cell GidMapper::gidToCell(unsigned gid, bool &ok) const
73 {
74     Cell result;
75 
76     // Read out the flags
77     result.setFlippedHorizontally(gid & FlippedHorizontallyFlag);
78     result.setFlippedVertically(gid & FlippedVerticallyFlag);
79     result.setFlippedAntiDiagonally(gid & FlippedAntiDiagonallyFlag);
80 
81     result.setRotatedHexagonal120(gid & RotatedHexagonal120Flag);
82 
83     // Clear the flags
84     gid &= ~(FlippedHorizontallyFlag |
85              FlippedVerticallyFlag |
86              FlippedAntiDiagonallyFlag |
87              RotatedHexagonal120Flag);
88 
89     if (gid == 0) {
90         ok = true;
91     } else if (isEmpty()) {
92         ok = false;
93     } else {
94         // Find the tileset containing this tile
95         QMap<unsigned, SharedTileset>::const_iterator i = mFirstGidToTileset.upperBound(gid);
96         if (i == mFirstGidToTileset.begin()) {
97             // Invalid global tile ID, since it lies before the first tileset
98             ok = false;
99         } else {
100             --i; // Navigate one tileset back since upper bound finds the next
101             int tileId = gid - i.key();
102             const SharedTileset &tileset = i.value();
103 
104             result.setTile(tileset.data(), tileId);
105             ok = true;
106 
107             // Adjust the next tile ID, in order to preserve tile references
108             // even to tilesets that failed to load.
109             tileset->setNextTileId(std::max(tileset->nextTileId(), tileId + 1));
110         }
111     }
112 
113     return result;
114 }
115 
116 /**
117  * Returns the global tile ID for the given \a cell. Returns 0 when the cell is
118  * empty or when its tileset isn't known.
119  */
cellToGid(const Cell & cell) const120 unsigned GidMapper::cellToGid(const Cell &cell) const
121 {
122     if (cell.isEmpty())
123         return 0;
124 
125     const Tileset *tileset = cell.tileset();
126 
127     // Find the first GID for the tileset
128     QMap<unsigned, SharedTileset>::const_iterator i = mFirstGidToTileset.begin();
129     QMap<unsigned, SharedTileset>::const_iterator i_end = mFirstGidToTileset.end();
130     while (i != i_end && i.value() != tileset)
131         ++i;
132 
133     if (i == i_end) // tileset not found
134         return 0;
135 
136     unsigned gid = i.key() + cell.tileId();
137     if (cell.flippedHorizontally())
138         gid |= FlippedHorizontallyFlag;
139     if (cell.flippedVertically())
140         gid |= FlippedVerticallyFlag;
141     if (cell.flippedAntiDiagonally())
142         gid |= FlippedAntiDiagonallyFlag;
143     if (cell.rotatedHexagonal120())
144         gid |= RotatedHexagonal120Flag;
145 
146     return gid;
147 }
148 
149 /**
150  * Encodes the tile layer data of the given \a tileLayer in the given
151  * \a format. This function should only be used for base64 encoding, with or
152  * without compression.
153  */
encodeLayerData(const TileLayer & tileLayer,Map::LayerDataFormat format,QRect bounds,int compressionLevel) const154 QByteArray GidMapper::encodeLayerData(const TileLayer &tileLayer,
155                                       Map::LayerDataFormat format,
156                                       QRect bounds, int compressionLevel) const
157 {
158     Q_ASSERT(format != Map::XML);
159     Q_ASSERT(format != Map::CSV);
160 
161     if (bounds.isEmpty())
162         bounds = QRect(0, 0, tileLayer.width(), tileLayer.height());
163 
164     QByteArray tileData;
165     tileData.reserve(bounds.width() * bounds.height() * 4);
166 
167     for (int y = bounds.top(); y <= bounds.bottom(); ++y) {
168         for (int x = bounds.left(); x <= bounds.right(); ++x) {
169             const unsigned gid = cellToGid(tileLayer.cellAt(x, y));
170             tileData.append(static_cast<char>(gid));
171             tileData.append(static_cast<char>(gid >> 8));
172             tileData.append(static_cast<char>(gid >> 16));
173             tileData.append(static_cast<char>(gid >> 24));
174         }
175     }
176 
177     if (format == Map::Base64Gzip)
178         tileData = compress(tileData, Gzip, compressionLevel);
179     else if (format == Map::Base64Zlib)
180         tileData = compress(tileData, Zlib, compressionLevel);
181     else if (format == Map::Base64Zstandard)
182         tileData = compress(tileData, Zstandard, compressionLevel);
183 
184     return tileData.toBase64();
185 }
186 
decodeLayerData(TileLayer & tileLayer,const QByteArray & layerData,Map::LayerDataFormat format,QRect bounds) const187 GidMapper::DecodeError GidMapper::decodeLayerData(TileLayer &tileLayer,
188                                                   const QByteArray &layerData,
189                                                   Map::LayerDataFormat format,
190                                                   QRect bounds) const
191 {
192     Q_ASSERT(format != Map::XML);
193     Q_ASSERT(format != Map::CSV);
194 
195     QByteArray decodedData = QByteArray::fromBase64(layerData);
196     const int size = bounds.width() * bounds.height() * 4;
197 
198     if (format == Map::Base64Gzip)
199         decodedData = decompress(decodedData, size, Gzip);
200     else if (format == Map::Base64Zlib)
201         decodedData = decompress(decodedData, size, Zlib);
202     else if (format == Map::Base64Zstandard)
203         decodedData = decompress(decodedData, size, Zstandard);
204 
205     if (size != decodedData.length())
206         return CorruptLayerData;
207 
208     const unsigned char *data = reinterpret_cast<const unsigned char*>(decodedData.constData());
209     int x = bounds.x();
210     int y = bounds.y();
211     bool ok;
212 
213     for (int i = 0; i < size - 3; i += 4) {
214         const unsigned gid = data[i] |
215                              data[i + 1] << 8 |
216                              data[i + 2] << 16 |
217                              data[i + 3] << 24;
218 
219         const Cell result = gidToCell(gid, ok);
220         if (!ok) {
221             mInvalidTile = gid;
222             return isEmpty() ? TileButNoTilesets : InvalidTile;
223         }
224 
225         tileLayer.setCell(x, y, result);
226 
227         x++;
228         if (x > bounds.right()) {
229             x = bounds.x();
230             y++;
231         }
232     }
233 
234     return NoError;
235 }
236