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