diff options
Diffstat (limited to 'libtiled')
33 files changed, 6072 insertions, 0 deletions
diff --git a/libtiled/compression.cpp b/libtiled/compression.cpp new file mode 100644 index 0000000..3cf6f46 --- /dev/null +++ b/libtiled/compression.cpp @@ -0,0 +1,162 @@ +/* + * compression.cpp + * Copyright 2008, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "compression.h" + +#include <zlib.h> +#include <QByteArray> +#include <QDebug> + +using namespace Tiled; + +// TODO: Improve error reporting by showing these errors in the user interface +static void logZlibError(int error) +{ + switch (error) + { + case Z_MEM_ERROR: + qDebug() << "Out of memory while (de)compressing data!"; + break; + case Z_VERSION_ERROR: + qDebug() << "Incompatible zlib version!"; + break; + case Z_NEED_DICT: + case Z_DATA_ERROR: + qDebug() << "Incorrect zlib compressed data!"; + break; + default: + qDebug() << "Unknown error while (de)compressing data!"; + } +} + +QByteArray Tiled::decompress(const QByteArray &data, int expectedSize) +{ + QByteArray out; + out.resize(expectedSize); + z_stream strm; + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.next_in = (Bytef *) data.data(); + strm.avail_in = data.length(); + strm.next_out = (Bytef *) out.data(); + strm.avail_out = out.size(); + + int ret = inflateInit2(&strm, 15 + 32); + + if (ret != Z_OK) { + logZlibError(ret); + return QByteArray(); + } + + do { + ret = inflate(&strm, Z_SYNC_FLUSH); + + switch (ret) { + case Z_NEED_DICT: + case Z_STREAM_ERROR: + ret = Z_DATA_ERROR; + case Z_DATA_ERROR: + case Z_MEM_ERROR: + inflateEnd(&strm); + logZlibError(ret); + return QByteArray(); + } + + if (ret != Z_STREAM_END) { + int oldSize = out.size(); + out.resize(out.size() * 2); + + strm.next_out = (Bytef *)(out.data() + oldSize); + strm.avail_out = oldSize; + } + } + while (ret != Z_STREAM_END); + + if (strm.avail_in != 0) { + logZlibError(Z_DATA_ERROR); + return QByteArray(); + } + + const int outLength = out.size() - strm.avail_out; + inflateEnd(&strm); + + out.resize(outLength); + return out; +} + +QByteArray Tiled::compress(const QByteArray &data, CompressionMethod method) +{ + QByteArray out; + out.resize(1024); + int err; + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.next_in = (Bytef *) data.data(); + strm.avail_in = data.length(); + strm.next_out = (Bytef *) out.data(); + strm.avail_out = out.size(); + + const int windowBits = (method == Gzip) ? 15 + 16 : 15; + + err = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, windowBits, + 8, Z_DEFAULT_STRATEGY); + if (err != Z_OK) { + logZlibError(err); + return QByteArray(); + } + + do { + err = deflate(&strm, Z_FINISH); + Q_ASSERT(err != Z_STREAM_ERROR); + + if (err == Z_OK) { + // More output space needed + int oldSize = out.size(); + out.resize(out.size() * 2); + + strm.next_out = (Bytef *)(out.data() + oldSize); + strm.avail_out = oldSize; + } + } while (err == Z_OK); + + if (err != Z_STREAM_END) { + logZlibError(err); + deflateEnd(&strm); + return QByteArray(); + } + + const int outLength = out.size() - strm.avail_out; + deflateEnd(&strm); + + out.resize(outLength); + return out; +} diff --git a/libtiled/compression.h b/libtiled/compression.h new file mode 100644 index 0000000..04071ee --- /dev/null +++ b/libtiled/compression.h @@ -0,0 +1,72 @@ +/* + * compression.h + * Copyright 2008, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef COMPRESSION_H +#define COMPRESSION_H + +#include "tiled_global.h" + +class QByteArray; + +namespace Tiled { + +enum CompressionMethod { + Gzip, + Zlib +}; + +/** + * Decompresses either zlib or gzip compressed memory. Returns a null + * QByteArray if decompressing failed. + * + * Needed because qUncompress does not support gzip compressed data. Also, + * this method does not need the expected size to be prepended to the data, + * but it can be passed as optional parameter. + * + * @param data the compressed data + * @param expectedSize the expected size of the uncompressed data in bytes + * @return the uncompressed data, or a null QByteArray if decompressing failed + */ +QByteArray TILEDSHARED_EXPORT decompress(const QByteArray &data, + int expectedSize = 1024); + +/** + * Compresses the give data in either gzip or zlib format. Returns a null + * QByteArray if compression failed. + * + * Needed because qCompress does not support gzip compression. + * + * @param data the uncompressed data + * @return the compressed data, or a null QByteArray if compression failed + */ +QByteArray TILEDSHARED_EXPORT compress(const QByteArray &data, + CompressionMethod method = Zlib); + +} // namespace Tiled + +#endif // COMPRESSION_H diff --git a/libtiled/gidmapper.cpp b/libtiled/gidmapper.cpp new file mode 100644 index 0000000..aca1a7f --- /dev/null +++ b/libtiled/gidmapper.cpp @@ -0,0 +1,125 @@ +/* + * gidmapper.cpp + * Copyright 2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * + * This file is part of libtiled. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gidmapper.h" + +#include "tile.h" +#include "tileset.h" +#include "map.h" + +using namespace Tiled; + +// Bits on the far end of the 32-bit global tile ID are used for tile flags +const int FlippedHorizontallyFlag = 0x80000000; +const int FlippedVerticallyFlag = 0x40000000; +const int FlippedAntiDiagonallyFlag = 0x20000000; + +GidMapper::GidMapper() +{ +} + +GidMapper::GidMapper(const QList<Tileset *> &tilesets) +{ + uint firstGid = 1; + foreach (Tileset *tileset, tilesets) { + insert(firstGid, tileset); + firstGid += tileset->tileCount(); + } +} + +Cell GidMapper::gidToCell(uint gid, bool &ok) const +{ + Cell result; + + // Read out the flags + result.flippedHorizontally = (gid & FlippedHorizontallyFlag); + result.flippedVertically = (gid & FlippedVerticallyFlag); + result.flippedAntiDiagonally = (gid & FlippedAntiDiagonallyFlag); + + // Clear the flags + gid &= ~(FlippedHorizontallyFlag | + FlippedVerticallyFlag | + FlippedAntiDiagonallyFlag); + + if (gid == 0) { + ok = true; + } else if (isEmpty()) { + ok = false; + } else { + // Find the tileset containing this tile + QMap<uint, Tileset*>::const_iterator i = mFirstGidToTileset.upperBound(gid); + --i; // Navigate one tileset back since upper bound finds the next + int tileId = gid - i.key(); + const Tileset *tileset = i.value(); + + if (tileset) { + const int columnCount = mTilesetColumnCounts.value(tileset); + if (columnCount > 0 && columnCount != tileset->columnCount()) { + // Correct tile index for changes in image width + const int row = tileId / columnCount; + const int column = tileId % columnCount; + tileId = row * tileset->columnCount() + column; + } + + result.tile = tileset->tileAt(tileId); + } else { + result.tile = 0; + } + + ok = true; + } + + return result; +} + +uint GidMapper::cellToGid(const Cell &cell) const +{ + if (cell.isEmpty()) + return 0; + + const Tileset *tileset = cell.tile->tileset(); + + // Find the first GID for the tileset + QMap<uint, Tileset*>::const_iterator i = mFirstGidToTileset.begin(); + QMap<uint, Tileset*>::const_iterator i_end = mFirstGidToTileset.end(); + while (i != i_end && i.value() != tileset) + ++i; + + if (i == i_end) // tileset not found + return 0; + + uint gid = i.key() + cell.tile->id(); + if (cell.flippedHorizontally) + gid |= FlippedHorizontallyFlag; + if (cell.flippedVertically) + gid |= FlippedVerticallyFlag; + if (cell.flippedAntiDiagonally) + gid |= FlippedAntiDiagonallyFlag; + + return gid; +} + +void GidMapper::setTilesetWidth(const Tileset *tileset, int width) +{ + if (tileset->tileWidth() == 0) + return; + + mTilesetColumnCounts.insert(tileset, tileset->columnCountForWidth(width)); +} diff --git a/libtiled/gidmapper.h b/libtiled/gidmapper.h new file mode 100644 index 0000000..803db67 --- /dev/null +++ b/libtiled/gidmapper.h @@ -0,0 +1,89 @@ +/* + * gidmapper.h + * Copyright 2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * + * This file is part of libtiled. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TILED_GIDMAPPER_H +#define TILED_GIDMAPPER_H + +#include "tilelayer.h" + +#include <QMap> + +namespace Tiled { + +/** + * A class that maps cells to global IDs (gids) and back. + */ +class TILEDSHARED_EXPORT GidMapper +{ +public: + /** + * Default constructor. Use \l insert to initialize the gid mapper + * incrementally. + */ + GidMapper(); + + /** + * Constructor that initializes the gid mapper using the given \a tilesets. + */ + GidMapper(const QList<Tileset *> &tilesets); + + /** + * Insert the given \a tileset with \a firstGid as its first global ID. + */ + void insert(uint firstGid, Tileset *tileset) + { mFirstGidToTileset.insert(firstGid, tileset); } + + /** + * Clears the gid mapper, so that it can be reused. + */ + void clear() { mFirstGidToTileset.clear(); } + + /** + * Returns true when no tilesets are known to this gid mapper. + */ + bool isEmpty() const { return mFirstGidToTileset.isEmpty(); } + + /** + * Returns the cell data matched by the given \a gid. The \a ok parameter + * indicates whether an error occurred. + */ + Cell gidToCell(uint gid, bool &ok) const; + + /** + * Returns the global tile ID for the given \a cell. Returns 0 when the + * cell is empty or when its tileset isn't known. + */ + uint cellToGid(const Cell &cell) const; + + /** + * This sets the original tileset width. In case the image size has + * changed, the tile indexes will be adjusted automatically when using + * gidToCell(). + */ + void setTilesetWidth(const Tileset *tileset, int width); + +private: + QMap<uint, Tileset*> mFirstGidToTileset; + QMap<const Tileset*, int> mTilesetColumnCounts; +}; + +} // namespace Tiled + +#endif // TILED_GIDMAPPER_H diff --git a/libtiled/isometricrenderer.cpp b/libtiled/isometricrenderer.cpp new file mode 100644 index 0000000..a7e182b --- /dev/null +++ b/libtiled/isometricrenderer.cpp @@ -0,0 +1,430 @@ +/* + * isometricrenderer.cpp + * Copyright 2009-2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "isometricrenderer.h" + +#include "map.h" +#include "mapobject.h" +#include "tile.h" +#include "tilelayer.h" +#include "tileset.h" + +#include <cmath> + +using namespace Tiled; + +QSize IsometricRenderer::mapSize() const +{ + // Map width and height contribute equally in both directions + const int side = map()->height() + map()->width(); + return QSize(side * map()->tileWidth() / 2, + side * map()->tileHeight() / 2); +} + +QRect IsometricRenderer::boundingRect(const QRect &rect) const +{ + const int tileWidth = map()->tileWidth(); + const int tileHeight = map()->tileHeight(); + + const int originX = map()->height() * tileWidth / 2; + const QPoint pos((rect.x() - (rect.y() + rect.height())) + * tileWidth / 2 + originX, + (rect.x() + rect.y()) * tileHeight / 2); + + const int side = rect.height() + rect.width(); + const QSize size(side * tileWidth / 2, + side * tileHeight / 2); + + return QRect(pos, size); +} + +QRectF IsometricRenderer::boundingRect(const MapObject *object) const +{ + if (object->tile()) { + const QPointF bottomCenter = tileToPixelCoords(object->position()); + const QPixmap &img = object->tile()->image(); + return QRectF(bottomCenter.x() - img.width() / 2, + bottomCenter.y() - img.height(), + img.width(), + img.height()).adjusted(-1, -1, 1, 1); + } else if (!object->polygon().isEmpty()) { + const QPointF &pos = object->position(); + const QPolygonF polygon = object->polygon().translated(pos); + const QPolygonF screenPolygon = tileToPixelCoords(polygon); + return screenPolygon.boundingRect().adjusted(-2, -2, 3, 3); + } else { + // Take the bounding rect of the projected object, and then add a few + // pixels on all sides to correct for the line width. + const QRectF base = tileRectToPolygon(object->bounds()).boundingRect(); + return base.adjusted(-2, -3, 2, 2); + } +} + +QPainterPath IsometricRenderer::shape(const MapObject *object) const +{ + QPainterPath path; + if (object->tile()) { + path.addRect(boundingRect(object)); + } else { + switch (object->shape()) { + case MapObject::Rectangle: + path.addPolygon(tileRectToPolygon(object->bounds())); + break; + case MapObject::Polygon: + case MapObject::Polyline: { + const QPointF &pos = object->position(); + const QPolygonF polygon = object->polygon().translated(pos); + const QPolygonF screenPolygon = tileToPixelCoords(polygon); + if (object->shape() == MapObject::Polygon) { + path.addPolygon(screenPolygon); + } else { + for (int i = 1; i < screenPolygon.size(); ++i) { + path.addPolygon(lineToPolygon(screenPolygon[i - 1], + screenPolygon[i])); + } + path.setFillRule(Qt::WindingFill); + } + break; + } + } + } + return path; +} + +void IsometricRenderer::drawGrid(QPainter *painter, const QRectF &rect) const +{ + const int tileWidth = map()->tileWidth(); + const int tileHeight = map()->tileHeight(); + + QRect r = rect.toAlignedRect(); + r.adjust(-tileWidth / 2, -tileHeight / 2, + tileWidth / 2, tileHeight / 2); + + const int startX = qMax(qreal(0), pixelToTileCoords(r.topLeft()).x()); + const int startY = qMax(qreal(0), pixelToTileCoords(r.topRight()).y()); + const int endX = qMin(qreal(map()->width()), + pixelToTileCoords(r.bottomRight()).x()); + const int endY = qMin(qreal(map()->height()), + pixelToTileCoords(r.bottomLeft()).y()); + + QColor gridColor(Qt::black); + gridColor.setAlpha(128); + + QPen gridPen(gridColor); + gridPen.setDashPattern(QVector<qreal>() << 2 << 2); + painter->setPen(gridPen); + + for (int y = startY; y <= endY; ++y) { + const QPointF start = tileToPixelCoords(startX, y); + const QPointF end = tileToPixelCoords(endX, y); + painter->drawLine(start, end); + } + for (int x = startX; x <= endX; ++x) { + const QPointF start = tileToPixelCoords(x, startY); + const QPointF end = tileToPixelCoords(x, endY); + painter->drawLine(start, end); + } +} + +void IsometricRenderer::drawTileLayer(QPainter *painter, + const TileLayer *layer, + const QRectF &exposed) const +{ + const int tileWidth = map()->tileWidth(); + const int tileHeight = map()->tileHeight(); + + if (tileWidth <= 0 || tileHeight <= 1) + return; + + QRect rect = exposed.toAlignedRect(); + if (rect.isNull()) + rect = boundingRect(layer->bounds()); + + QMargins drawMargins = layer->drawMargins(); + drawMargins.setTop(drawMargins.top() - tileHeight); + drawMargins.setRight(drawMargins.right() - tileWidth); + + rect.adjust(-drawMargins.right(), + -drawMargins.bottom(), + drawMargins.left(), + drawMargins.top()); + + // Determine the tile and pixel coordinates to start at + QPointF tilePos = pixelToTileCoords(rect.x(), rect.y()); + QPoint rowItr = QPoint((int) std::floor(tilePos.x()), + (int) std::floor(tilePos.y())); + QPointF startPos = tileToPixelCoords(rowItr); + startPos.rx() -= tileWidth / 2; + startPos.ry() += tileHeight; + + // Compensate for the layer position + rowItr -= QPoint(layer->x(), layer->y()); + + /* Determine in which half of the tile the top-left corner of the area we + * need to draw is. If we're in the upper half, we need to start one row + * up due to those tiles being visible as well. How we go up one row + * depends on whether we're in the left or right half of the tile. + */ + const bool inUpperHalf = startPos.y() - rect.y() > tileHeight / 2; + const bool inLeftHalf = rect.x() - startPos.x() < tileWidth / 2; + + if (inUpperHalf) { + if (inLeftHalf) { + --rowItr.rx(); + startPos.rx() -= tileWidth / 2; + } else { + --rowItr.ry(); + startPos.rx() += tileWidth / 2; + } + startPos.ry() -= tileHeight / 2; + } + + // Determine whether the current row is shifted half a tile to the right + bool shifted = inUpperHalf ^ inLeftHalf; + + QTransform baseTransform = painter->transform(); + + for (int y = startPos.y(); y - tileHeight < rect.bottom(); + y += tileHeight / 2) + { + QPoint columnItr = rowItr; + + for (int x = startPos.x(); x < rect.right(); x += tileWidth) { + if (layer->contains(columnItr)) { + const Cell &cell = layer->cellAt(columnItr); + if (!cell.isEmpty()) { + const QPixmap &img = cell.tile->image(); + const QPoint offset = cell.tile->tileset()->tileOffset(); + + qreal m11 = 1; // Horizontal scaling factor + qreal m12 = 0; // Vertical shearing factor + qreal m21 = 0; // Horizontal shearing factor + qreal m22 = 1; // Vertical scaling factor + qreal dx = offset.x() + x; + qreal dy = offset.y() + y - img.height(); + + if (cell.flippedAntiDiagonally) { + // Use shearing to swap the X/Y axis + m11 = 0; + m12 = 1; + m21 = 1; + m22 = 0; + + // Compensate for the swap of image dimensions + dy += img.height() - img.width(); + } + if (cell.flippedHorizontally) { + m11 = -m11; + m21 = -m21; + dx += cell.flippedAntiDiagonally ? img.height() + : img.width(); + } + if (cell.flippedVertically) { + m12 = -m12; + m22 = -m22; + dy += cell.flippedAntiDiagonally ? img.width() + : img.height(); + } + + const QTransform transform(m11, m12, m21, m22, dx, dy); + painter->setTransform(transform * baseTransform); + + painter->drawPixmap(0, 0, img); + } + } + + // Advance to the next column + ++columnItr.rx(); + --columnItr.ry(); + } + + // Advance to the next row + if (!shifted) { + ++rowItr.rx(); + startPos.rx() += tileWidth / 2; + shifted = true; + } else { + ++rowItr.ry(); + startPos.rx() -= tileWidth / 2; + shifted = false; + } + } + + painter->setTransform(baseTransform); +} + +void IsometricRenderer::drawTileSelection(QPainter *painter, + const QRegion ®ion, + const QColor &color, + const QRectF &exposed) const +{ + painter->setBrush(color); + painter->setPen(Qt::NoPen); + foreach (const QRect &r, region.rects()) { + QPolygonF polygon = tileRectToPolygon(r); + if (QRectF(polygon.boundingRect()).intersects(exposed)) + painter->drawConvexPolygon(polygon); + } +} + +void IsometricRenderer::drawMapObject(QPainter *painter, + const MapObject *object, + const QColor &color) const +{ + painter->save(); + + QPen pen(Qt::black); + + if (object->tile()) { + const QPixmap &img = object->tile()->image(); + QPointF paintOrigin(-img.width() / 2, -img.height()); + paintOrigin += tileToPixelCoords(object->position()).toPoint(); + painter->drawPixmap(paintOrigin, img); + + pen.setStyle(Qt::SolidLine); + painter->setPen(pen); + painter->drawRect(QRectF(paintOrigin, img.size())); + pen.setStyle(Qt::DotLine); + pen.setColor(color); + painter->setPen(pen); + painter->drawRect(QRectF(paintOrigin, img.size())); + } else { + QColor brushColor = color; + brushColor.setAlpha(50); + QBrush brush(brushColor); + + pen.setJoinStyle(Qt::RoundJoin); + pen.setCapStyle(Qt::RoundCap); + pen.setWidth(2); + + painter->setPen(pen); + painter->setRenderHint(QPainter::Antialiasing); + + // TODO: Draw the object name + // TODO: Do something sensible to make null-sized objects usable + + switch (object->shape()) { + case MapObject::Rectangle: { + QPolygonF polygon = tileRectToPolygon(object->bounds()); + painter->drawPolygon(polygon); + + pen.setColor(color); + painter->setPen(pen); + painter->setBrush(brush); + polygon.translate(0, -1); + + painter->drawPolygon(polygon); + break; + } + case MapObject::Polygon: { + const QPointF &pos = object->position(); + const QPolygonF polygon = object->polygon().translated(pos); + QPolygonF screenPolygon = tileToPixelCoords(polygon); + + painter->drawPolygon(screenPolygon); + + pen.setColor(color); + painter->setPen(pen); + painter->setBrush(brush); + screenPolygon.translate(0, -1); + + painter->drawPolygon(screenPolygon); + break; + } + case MapObject::Polyline: { + const QPointF &pos = object->position(); + const QPolygonF polygon = object->polygon().translated(pos); + QPolygonF screenPolygon = tileToPixelCoords(polygon); + + painter->drawPolyline(screenPolygon); + + pen.setColor(color); + painter->setPen(pen); + screenPolygon.translate(0, -1); + + painter->drawPolyline(screenPolygon); + break; + } + } + } + + painter->restore(); +} + +QPointF IsometricRenderer::pixelToTileCoords(qreal x, qreal y) const +{ + const int tileWidth = map()->tileWidth(); + const int tileHeight = map()->tileHeight(); + const qreal ratio = (qreal) tileWidth / tileHeight; + + x -= map()->height() * tileWidth / 2; + const qreal mx = y + (x / ratio); + const qreal my = y - (x / ratio); + + return QPointF(mx / tileHeight, + my / tileHeight); +} + +QPointF IsometricRenderer::tileToPixelCoords(qreal x, qreal y) const +{ + const int tileWidth = map()->tileWidth(); + const int tileHeight = map()->tileHeight(); + const int originX = map()->height() * tileWidth / 2; + + return QPointF((x - y) * tileWidth / 2 + originX, + (x + y) * tileHeight / 2); +} + +QPolygonF IsometricRenderer::tileRectToPolygon(const QRect &rect) const +{ + const int tileWidth = map()->tileWidth(); + const int tileHeight = map()->tileHeight(); + + const QPointF topRight = tileToPixelCoords(rect.topRight()); + const QPointF bottomRight = tileToPixelCoords(rect.bottomRight()); + const QPointF bottomLeft = tileToPixelCoords(rect.bottomLeft()); + + QPolygonF polygon; + polygon << QPointF(tileToPixelCoords(rect.topLeft())); + polygon << QPointF(topRight.x() + tileWidth / 2, + topRight.y() + tileHeight / 2); + polygon << QPointF(bottomRight.x(), bottomRight.y() + tileHeight); + polygon << QPointF(bottomLeft.x() - tileWidth / 2, + bottomLeft.y() + tileHeight / 2); + return polygon; +} + +QPolygonF IsometricRenderer::tileRectToPolygon(const QRectF &rect) const +{ + QPolygonF polygon; + polygon << QPointF(tileToPixelCoords(rect.topLeft())); + polygon << QPointF(tileToPixelCoords(rect.topRight())); + polygon << QPointF(tileToPixelCoords(rect.bottomRight())); + polygon << QPointF(tileToPixelCoords(rect.bottomLeft())); + return polygon; +} diff --git a/libtiled/isometricrenderer.h b/libtiled/isometricrenderer.h new file mode 100644 index 0000000..5cb5d35 --- /dev/null +++ b/libtiled/isometricrenderer.h @@ -0,0 +1,82 @@ +/* + * isometricrenderer.h + * Copyright 2009-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ISOMETRICRENDERER_H +#define ISOMETRICRENDERER_H + +#include "maprenderer.h" + +namespace Tiled { + +/** + * An isometric map renderer. + * + * Isometric maps have diamond shaped tiles. This map renderer renders them in + * such a way that the map will also be diamond shaped. The X axis points to + * the bottom right while the Y axis points to the bottom left. + */ +class TILEDSHARED_EXPORT IsometricRenderer : public MapRenderer +{ +public: + IsometricRenderer(const Map *map) : MapRenderer(map) {} + + QSize mapSize() const; + + QRect boundingRect(const QRect &rect) const; + + QRectF boundingRect(const MapObject *object) const; + QPainterPath shape(const MapObject *object) const; + + void drawGrid(QPainter *painter, const QRectF &rect) const; + + void drawTileLayer(QPainter *painter, const TileLayer *layer, + const QRectF &exposed = QRectF()) const; + + void drawTileSelection(QPainter *painter, + const QRegion ®ion, + const QColor &color, + const QRectF &exposed) const; + + void drawMapObject(QPainter *painter, + const MapObject *object, + const QColor &color) const; + + using MapRenderer::pixelToTileCoords; + QPointF pixelToTileCoords(qreal x, qreal y) const; + + using MapRenderer::tileToPixelCoords; + QPointF tileToPixelCoords(qreal x, qreal y) const; + +private: + QPolygonF tileRectToPolygon(const QRect &rect) const; + QPolygonF tileRectToPolygon(const QRectF &rect) const; +}; + +} // namespace Tiled + +#endif // ISOMETRICRENDERER_H diff --git a/libtiled/layer.cpp b/libtiled/layer.cpp new file mode 100644 index 0000000..a019086 --- /dev/null +++ b/libtiled/layer.cpp @@ -0,0 +1,69 @@ +/* + * layer.cpp + * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * Copyright 2009, Jeff Bland <jeff@teamphobic.com> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "layer.h" + +using namespace Tiled; + +Layer::Layer(const QString &name, int x, int y, int width, int height): + mName(name), + mX(x), + mY(y), + mWidth(width), + mHeight(height), + mOpacity(1.0f), + mVisible(true), + mMap(0) +{ +} + +void Layer::resize(const QSize &size, const QPoint & /* offset */) +{ + mWidth = size.width(); + mHeight = size.height(); +} + +/** + * A helper function for initializing the members of the given instance to + * those of this layer. Used by subclasses when cloning. + * + * Layer name, position and size are not cloned, since they are assumed to have + * already been passed to the constructor. Also, map ownership is not cloned, + * since the clone is not added to the map. + * + * \return the initialized clone (the same instance that was passed in) + * \sa clone() + */ +Layer *Layer::initializeClone(Layer *clone) const +{ + clone->mOpacity = mOpacity; + clone->mVisible = mVisible; + clone->setProperties(properties()); + return clone; +} diff --git a/libtiled/layer.h b/libtiled/layer.h new file mode 100644 index 0000000..2aeac27 --- /dev/null +++ b/libtiled/layer.h @@ -0,0 +1,215 @@ +/* + * layer.h + * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * Copyright 2009, Jeff Bland <jeff@teamphobic.com> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LAYER_H +#define LAYER_H + +#include "object.h" + +#include <QPixmap> +#include <QRect> +#include <QSet> +#include <QString> +#include <QVector> + +namespace Tiled { + +class Map; +class ObjectGroup; +class TileLayer; +class Tileset; + +/** + * A map layer. + */ +class TILEDSHARED_EXPORT Layer : public Object +{ +public: + /** + * Constructor. + */ + Layer(const QString &name, int x, int y, int width, int height); + + /** + * Returns the name of this layer. + */ + const QString &name() const { return mName; } + + /** + * Sets the name of this layer. + */ + void setName(const QString &name) { mName = name; } + + /** + * Returns the opacity of this layer. + */ + float opacity() const { return mOpacity; } + + /** + * Sets the opacity of this layer. + */ + void setOpacity(float opacity) { mOpacity = opacity; } + + /** + * Returns the visibility of this layer. + */ + bool isVisible() const { return mVisible; } + + /** + * Sets the visibility of this layer. + */ + void setVisible(bool visible) { mVisible = visible; } + + /** + * Returns the map this layer is part of. + */ + Map *map() const { return mMap; } + + /** + * Sets the map this layer is part of. Should only be called from the + * Map class. + */ + void setMap(Map *map) { mMap = map; } + + /** + * Returns the x position of this layer (in tiles). + */ + int x() const { return mX; } + + /** + * Sets the x position of this layer (in tiles). + */ + void setX(int x) { mX = x; } + + /** + * Returns the y position of this layer (in tiles). + */ + int y() const { return mY; } + + /** + * Sets the y position of this layer (in tiles). + */ + void setY(int y) { mY = y; } + + /** + * Returns the position of this layer (in tiles). + */ + QPoint position() const { return QPoint(mX, mY); } + + /** + * Sets the position of this layer (in tiles). + */ + void setPosition(QPoint pos) { setPosition(pos.x(), pos.y()); } + void setPosition(int x, int y) { mX = x; mY = y; } + + /** + * Returns the width of this layer. + */ + int width() const { return mWidth; } + + /** + * Returns the height of this layer. + */ + int height() const { return mHeight; } + + /** + * Returns the bounds of this layer. + */ + QRect bounds() const { return QRect(mX, mY, mWidth, mHeight); } + + /** + * Computes and returns the set of tilesets used by this layer. + */ + virtual QSet<Tileset*> usedTilesets() const = 0; + + /** + * Returns whether this layer is referencing the given tileset. + */ + virtual bool referencesTileset(const Tileset *tileset) const = 0; + + /** + * Replaces all references to tiles from \a oldTileset with tiles from + * \a newTileset. + */ + virtual void replaceReferencesToTileset(Tileset *oldTileset, + Tileset *newTileset) = 0; + + /** + * Resizes this layer to \a size, while shifting its contents by \a offset. + * Note that the position of the layer remains unaffected. + */ + virtual void resize(const QSize &size, const QPoint &offset); + + /** + * Offsets the layer by the given amount, and optionally wraps it around. + */ + virtual void offset(const QPoint &offset, const QRect &bounds, + bool wrapX, bool wrapY) = 0; + + /** + * Returns whether this layer can merge together with the \a other layer. + */ + virtual bool canMergeWith(Layer *other) const = 0; + + /** + * Returns a newly allocated layer that is the result of merging this layer + * with the \a other layer. Where relevant, the other layer is considered + * to be on top of this one. + * + * Should only be called when canMergeWith returns true. + */ + virtual Layer *mergedWith(Layer *other) const = 0; + + /** + * Returns a duplicate of this layer. The caller is responsible for the + * ownership of this newly created layer. + */ + virtual Layer *clone() const = 0; + + // These functions allow checking whether this Layer is an instance of the + // given subclass without relying on a dynamic_cast. + virtual TileLayer *asTileLayer() { return 0; } + virtual ObjectGroup *asObjectGroup() { return 0; } + +protected: + Layer *initializeClone(Layer *clone) const; + + QString mName; + int mX; + int mY; + int mWidth; + int mHeight; + float mOpacity; + bool mVisible; + Map *mMap; +}; + +} // namespace Tiled + +#endif // LAYER_H diff --git a/libtiled/libtiled.pri b/libtiled/libtiled.pri new file mode 100644 index 0000000..bdce5f2 --- /dev/null +++ b/libtiled/libtiled.pri @@ -0,0 +1,3 @@ +INCLUDEPATH += $$PWD +DEPENDPATH += $$PWD +LIBS *= -ltiled diff --git a/libtiled/libtiled.pro b/libtiled/libtiled.pro new file mode 100644 index 0000000..84341d4 --- /dev/null +++ b/libtiled/libtiled.pro @@ -0,0 +1,58 @@ +include(../platformer.pri) + +TEMPLATE = lib +TARGET = tiled +target.path = $${LIBDIR} +INSTALLS += target +macx { + DESTDIR = ../bin/Tiled.app/Contents/Frameworks + QMAKE_LFLAGS_SONAME = -Wl,-install_name,@executable_path/../Frameworks/ +} else { + DESTDIR = ../lib +} +DLLDESTDIR = .. + +#win32:INCLUDEPATH += $$(QTDIR)/src/3rdparty/zlib +win32:INCLUDEPATH += G:/QtSDK/QtSources/4.8.1/src/3rdparty/zlib +else:LIBS += -lz + +DEFINES += QT_NO_CAST_FROM_ASCII \ + QT_NO_CAST_TO_ASCII +DEFINES += TILED_LIBRARY +contains(QT_CONFIG, reduce_exports): CONFIG += hide_symbols +OBJECTS_DIR = .obj +SOURCES += compression.cpp \ + isometricrenderer.cpp \ + layer.cpp \ + map.cpp \ + mapobject.cpp \ + mapreader.cpp \ + maprenderer.cpp \ + mapwriter.cpp \ + objectgroup.cpp \ + orthogonalrenderer.cpp \ + properties.cpp \ + tilelayer.cpp \ + tileset.cpp \ + gidmapper.cpp +HEADERS += compression.h \ + isometricrenderer.h \ + layer.h \ + map.h \ + mapobject.h \ + mapreader.h \ + maprenderer.h \ + mapwriter.h \ + object.h \ + objectgroup.h \ + orthogonalrenderer.h \ + properties.h \ + tile.h \ + tiled_global.h \ + tilelayer.h \ + tileset.h \ + gidmapper.h +macx { + contains(QT_CONFIG, ppc):CONFIG += x86 \ + ppc +} diff --git a/libtiled/map.cpp b/libtiled/map.cpp new file mode 100644 index 0000000..07abd81 --- /dev/null +++ b/libtiled/map.cpp @@ -0,0 +1,207 @@ +/* + * map.cpp + * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * Copyright 2008, Roderic Morris <roderic@ccs.neu.edu> + * Copyright 2010, Andrew G. Crowell <overkill9999@gmail.com> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "map.h" + +#include "layer.h" +#include "tile.h" +#include "tilelayer.h" +#include "tileset.h" + +using namespace Tiled; + +Map::Map(Orientation orientation, + int width, int height, int tileWidth, int tileHeight): + mOrientation(orientation), + mWidth(width), + mHeight(height), + mTileWidth(tileWidth), + mTileHeight(tileHeight) +{ +} + +Map::~Map() +{ + qDeleteAll(mLayers); +} + +static QMargins maxMargins(const QMargins &a, + const QMargins &b) +{ + return QMargins(qMax(a.left(), b.left()), + qMax(a.top(), b.top()), + qMax(a.right(), b.right()), + qMax(a.bottom(), b.bottom())); +} + +void Map::adjustDrawMargins(const QMargins &margins) +{ + // The TileLayer includes the maximum tile size in its draw margins. So + // we need to subtract the tile size of the map, since that part does not + // contribute to additional margin. + mDrawMargins = maxMargins(QMargins(margins.left(), + margins.top() - mTileHeight, + margins.right() - mTileWidth, + margins.bottom()), + mDrawMargins); +} + +int Map::tileLayerCount() const +{ + int count = 0; + foreach (Layer *layer, mLayers) + if (layer->asTileLayer()) + count++; + return count; +} + +int Map::objectGroupCount() const +{ + int count = 0; + foreach (Layer *layer, mLayers) + if (layer->asObjectGroup()) + count++; + return count; +} + +void Map::addLayer(Layer *layer) +{ + adoptLayer(layer); + mLayers.append(layer); +} + +int Map::indexOfLayer(const QString &layerName) const +{ + for (int index = 0; index < mLayers.size(); index++) + if (layerAt(index)->name() == layerName) + return index; + + return -1; +} + +void Map::insertLayer(int index, Layer *layer) +{ + adoptLayer(layer); + mLayers.insert(index, layer); +} + +void Map::adoptLayer(Layer *layer) +{ + layer->setMap(this); + + if (TileLayer *tileLayer = dynamic_cast<TileLayer*>(layer)) + adjustDrawMargins(tileLayer->drawMargins()); +} + +Layer *Map::takeLayerAt(int index) +{ + Layer *layer = mLayers.takeAt(index); + layer->setMap(0); + return layer; +} + +void Map::addTileset(Tileset *tileset) +{ + mTilesets.append(tileset); +} + +void Map::insertTileset(int index, Tileset *tileset) +{ + mTilesets.insert(index, tileset); +} + +int Map::indexOfTileset(Tileset *tileset) const +{ + return mTilesets.indexOf(tileset); +} + +void Map::removeTilesetAt(int index) +{ + mTilesets.removeAt(index); +} + +void Map::replaceTileset(Tileset *oldTileset, Tileset *newTileset) +{ + const int index = mTilesets.indexOf(oldTileset); + Q_ASSERT(index != -1); + + foreach (Layer *layer, mLayers) + layer->replaceReferencesToTileset(oldTileset, newTileset); + + mTilesets.replace(index, newTileset); +} + +bool Map::isTilesetUsed(Tileset *tileset) const +{ + foreach (const Layer *layer, mLayers) + if (layer->referencesTileset(tileset)) + return true; + + return false; +} + +Map *Map::clone() const +{ + Map *o = new Map(mOrientation, mWidth, mHeight, mTileWidth, mTileHeight); + o->mDrawMargins = mDrawMargins; + foreach (const Layer *layer, mLayers) + o->addLayer(layer->clone()); + o->mTilesets = mTilesets; + o->setProperties(properties()); + return o; +} + + +QString Tiled::orientationToString(Map::Orientation orientation) +{ + switch (orientation) { + default: + case Map::Unknown: + return QLatin1String("unknown"); + break; + case Map::Orthogonal: + return QLatin1String("orthogonal"); + break; + case Map::Isometric: + return QLatin1String("isometric"); + break; + } +} + +Map::Orientation Tiled::orientationFromString(const QString &string) +{ + Map::Orientation orientation = Map::Unknown; + if (string == QLatin1String("orthogonal")) { + orientation = Map::Orthogonal; + } else if (string == QLatin1String("isometric")) { + orientation = Map::Isometric; + } + return orientation; +} diff --git a/libtiled/map.h b/libtiled/map.h new file mode 100644 index 0000000..d209816 --- /dev/null +++ b/libtiled/map.h @@ -0,0 +1,278 @@ +/* + * map.h + * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * Copyright 2008, Roderic Morris <roderic@ccs.neu.edu> + * Copyright 2010, Andrew G. Crowell <overkill9999@gmail.com> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MAP_H +#define MAP_H + +#include "object.h" + +#include <QList> +#include <QMargins> +#include <QSize> + +namespace Tiled { + +class Layer; +class Tile; +class Tileset; +class ObjectGroup; + +/** + * A tile map. Consists of a stack of layers, each can be either a TileLayer + * or an ObjectGroup. + * + * It also keeps track of the list of referenced tilesets. + */ +class TILEDSHARED_EXPORT Map : public Object +{ +public: + /** + * The orientation of the map determines how it should be rendered. An + * Orthogonal map is using rectangular tiles that are aligned on a + * straight grid. An Isometric map uses diamond shaped tiles that are + * aligned on an isometric projected grid. A Hexagonal map uses hexagon + * shaped tiles that fit into each other by shifting every other row. + * + * Only Orthogonal and Isometric maps are supported by this version of + * Tiled. + */ + enum Orientation { + Unknown, + Orthogonal, + Isometric + }; + + /** + * Constructor, taking map orientation, size and tile size as parameters. + */ + Map(Orientation orientation, + int width, int height, + int tileWidth, int tileHeight); + + /** + * Destructor. + */ + ~Map(); + + /** + * Returns the orientation of the map. + */ + Orientation orientation() const { return mOrientation; } + + /** + * Sets the orientation of the map. + */ + void setOrientation(Orientation orientation) + { mOrientation = orientation; } + + /** + * Returns the width of this map. + */ + int width() const { return mWidth; } + + /** + * Sets the width of this map. + */ + void setWidth(int width) { mWidth = width; } + + /** + * Returns the height of this map. + */ + int height() const { return mHeight; } + + /** + * Sets the height of this map. + */ + void setHeight(int height) { mHeight = height; } + + /** + * Returns the size of this map. Provided for convenience. + */ + QSize size() const { return QSize(mWidth, mHeight); } + + /** + * Returns the tile width of this map. + */ + int tileWidth() const { return mTileWidth; } + + /** + * Returns the tile height used by this map. + */ + int tileHeight() const { return mTileHeight; } + + /** + * Adjusts the draw margins to be at least as big as the given margins. + * Called from tile layers when their tiles change. + */ + void adjustDrawMargins(const QMargins &margins); + + /** + * Returns the margins that have to be taken into account when figuring + * out which part of the map to repaint after changing some tiles. + * + * @see TileLayer::drawMargins + */ + QMargins drawMargins() const { return mDrawMargins; } + + /** + * Returns the number of layers of this map. + */ + int layerCount() const + { return mLayers.size(); } + + /** + * Convenience function that returns the number of layers of this map that + * are tile layers. + */ + int tileLayerCount() const; + + /** + * Convenience function that returns the number of layers of this map that + * are object groups. + */ + int objectGroupCount() const; + + /** + * Returns the layer at the specified index. + */ + Layer *layerAt(int index) const + { return mLayers.at(index); } + + /** + * Returns the list of layers of this map. This is useful when you want to + * use foreach. + */ + const QList<Layer*> &layers() const { return mLayers; } + + /** + * Adds a layer to this map. + */ + void addLayer(Layer *layer); + + /** + * Returns the index of the layer given by \a layerName, or -1 if no + * layer with that name is found. + */ + int indexOfLayer(const QString &layerName) const; + + /** + * Adds a layer to this map, inserting it at the given index. + */ + void insertLayer(int index, Layer *layer); + + /** + * Removes the layer at the given index from this map and returns it. + * The caller becomes responsible for the lifetime of this layer. + */ + Layer *takeLayerAt(int index); + + /** + * Adds a tileset to this map. The map does not take ownership over its + * tilesets, this is merely for keeping track of which tilesets are used by + * the map, and their saving order. + * + * @param tileset the tileset to add + */ + void addTileset(Tileset *tileset); + + /** + * Inserts \a tileset at \a index in the list of tilesets used by this map. + */ + void insertTileset(int index, Tileset *tileset); + + /** + * Returns the index of the given \a tileset, or -1 if it is not used in + * this map. + */ + int indexOfTileset(Tileset *tileset) const; + + /** + * Removes the tileset at \a index from this map. + * + * \warning Does not make sure that this map no longer refers to tiles from + * the removed tileset! + * + * \sa addTileset + */ + void removeTilesetAt(int index); + + /** + * Replaces all tiles from \a oldTileset with tiles from \a newTileset. + * Also replaces the old tileset with the new tileset in the list of + * tilesets. + */ + void replaceTileset(Tileset *oldTileset, Tileset *newTileset); + + /** + * Returns the tilesets that the tiles on this map are using. + */ + const QList<Tileset*> &tilesets() const { return mTilesets; } + + /** + * Returns whether the given \a tileset is used by any tile layer of this + * map. + */ + bool isTilesetUsed(Tileset *tileset) const; + + Map *clone() const; + +private: + void adoptLayer(Layer *layer); + + Orientation mOrientation; + int mWidth; + int mHeight; + int mTileWidth; + int mTileHeight; + QMargins mDrawMargins; + QList<Layer*> mLayers; + QList<Tileset*> mTilesets; +}; + +/** + * Helper function that converts the map orientation to a string value. Useful + * for map writers. + * + * @return The map orientation as a lowercase string. + */ +TILEDSHARED_EXPORT QString orientationToString(Map::Orientation); + +/** + * Helper function that converts a string to a map orientation enumerator. + * Useful for map readers. + * + * @return The map orientation matching the given string, or Map::Unknown if + * the string is unrecognized. + */ +TILEDSHARED_EXPORT Map::Orientation orientationFromString(const QString &); + +} // namespace Tiled + +#endif // MAP_H diff --git a/libtiled/mapobject.cpp b/libtiled/mapobject.cpp new file mode 100644 index 0000000..3a05073 --- /dev/null +++ b/libtiled/mapobject.cpp @@ -0,0 +1,63 @@ +/* + * mapobject.cpp + * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * Copyright 2008, Roderic Morris <roderic@ccs.neu.edu> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "mapobject.h" + +using namespace Tiled; + +MapObject::MapObject(): + mSize(0, 0), + mShape(Rectangle), + mTile(0), + mObjectGroup(0) +{ +} + +MapObject::MapObject(const QString &name, const QString &type, + const QPointF &pos, + const QSizeF &size): + mName(name), + mType(type), + mPos(pos), + mSize(size), + mShape(Rectangle), + mTile(0), + mObjectGroup(0) +{ +} + +MapObject *MapObject::clone() const +{ + MapObject *o = new MapObject(mName, mType, mPos, mSize); + o->setProperties(properties()); + o->setPolygon(mPolygon); + o->setShape(mShape); + o->setTile(mTile); + return o; +} diff --git a/libtiled/mapobject.h b/libtiled/mapobject.h new file mode 100644 index 0000000..aa14190 --- /dev/null +++ b/libtiled/mapobject.h @@ -0,0 +1,244 @@ +/* + * mapobject.h + * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * Copyright 2008, Roderic Morris <roderic@ccs.neu.edu> + * Copyright 2009, Jeff Bland <jeff@teamphobic.com> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MAPOBJECT_H +#define MAPOBJECT_H + +#include "object.h" + +#include <QPolygonF> +#include <QSizeF> +#include <QString> +#include <QRectF> + +namespace Tiled { + +class ObjectGroup; +class Tile; + +/** + * An object on a map. Objects are positioned and scaled using floating point + * values, ensuring they are not limited to the tile grid. They are suitable + * for adding any kind of annotation to your maps, as well as free placement of + * images. + * + * Common usages of objects include defining portals, monsters spawn areas, + * ambient effects, scripted areas, etc. + */ +class TILEDSHARED_EXPORT MapObject : public Object +{ +public: + /** + * Enumerates the different object shapes. Rectangle is the default shape. + * When a polygon is set, the shape determines whether it should be + * interpreted as a filled polygon or a line. + */ + enum Shape { + Rectangle, + Polygon, + Polyline + }; + + /** + * Default constructor. + */ + MapObject(); + + /** + * Constructor. + */ + MapObject(const QString &name, const QString &type, + const QPointF &pos, + const QSizeF &size); + + /** + * Destructor. + */ + ~MapObject() {} + + /** + * Returns the name of this object. The name is usually just used for + * identification of the object in the editor. + */ + const QString &name() const { return mName; } + + /** + * Sets the name of this object. + */ + void setName(const QString &name) { mName = name; } + + /** + * Returns the type of this object. The type usually says something about + * how the object is meant to be interpreted by the engine. + */ + const QString &type() const { return mType; } + + /** + * Sets the type of this object. + */ + void setType(const QString &type) { mType = type; } + + /** + * Returns the position of this object. + */ + const QPointF &position() const { return mPos; } + + /** + * Sets the position of this object. + */ + void setPosition(const QPointF &pos) { mPos = pos; } + + /** + * Returns the x position of this object. + */ + qreal x() const { return mPos.x(); } + + /** + * Sets the x position of this object. + */ + void setX(qreal x) { mPos.setX(x); } + + /** + * Returns the y position of this object. + */ + qreal y() const { return mPos.y(); } + + /** + * Sets the x position of this object. + */ + void setY(qreal y) { mPos.setY(y); } + + /** + * Returns the size of this object. + */ + const QSizeF &size() const { return mSize; } + + /** + * Sets the size of this object. + */ + void setSize(const QSizeF &size) { mSize = size; } + + void setSize(qreal width, qreal height) + { setSize(QSizeF(width, height)); } + + /** + * Returns the width of this object. + */ + qreal width() const { return mSize.width(); } + + /** + * Sets the width of this object. + */ + void setWidth(qreal width) { mSize.setWidth(width); } + + /** + * Returns the height of this object. + */ + qreal height() const { return mSize.height(); } + + /** + * Sets the height of this object. + */ + void setHeight(qreal height) { mSize.setHeight(height); } + + /** + * Sets the polygon associated with this object. The polygon is only used + * when the object shape is set to either Polygon or Polyline. + * + * \sa setShape() + */ + void setPolygon(const QPolygonF &polygon) { mPolygon = polygon; } + + /** + * Returns the polygon associated with this object. Returns an empty + * polygon when no polygon is associated with this object. + */ + const QPolygonF &polygon() const { return mPolygon; } + + /** + * Sets the shape of the object. + */ + void setShape(Shape shape) { mShape = shape; } + + /** + * Returns the shape of the object. + */ + Shape shape() const { return mShape; } + + /** + * Shortcut to getting a QRectF from position() and size(). + */ + QRectF bounds() const { return QRectF(mPos, mSize); } + + /** + * Sets the tile that is associated with this object. The object will + * display as the tile image. + * + * \warning The object shape is ignored for tile objects! + */ + void setTile(Tile *tile) { mTile = tile; } + + /** + * Returns the tile associated with this object. + */ + Tile *tile() const { return mTile; } + + /** + * Returns the object group this object belongs to. + */ + ObjectGroup *objectGroup() const { return mObjectGroup; } + + /** + * Sets the object group this object belongs to. Should only be called + * from the ObjectGroup class. + */ + void setObjectGroup(ObjectGroup *objectGroup) + { mObjectGroup = objectGroup; } + + /** + * Returns a duplicate of this object. The caller is responsible for the + * ownership of this newly created object. + */ + MapObject *clone() const; + +private: + QString mName; + QString mType; + QPointF mPos; + QSizeF mSize; + QPolygonF mPolygon; + Shape mShape; + Tile *mTile; + ObjectGroup *mObjectGroup; +}; + +} // namespace Tiled + +#endif // MAPOBJECT_H diff --git a/libtiled/mapreader.cpp b/libtiled/mapreader.cpp new file mode 100644 index 0000000..ca8e19c --- /dev/null +++ b/libtiled/mapreader.cpp @@ -0,0 +1,772 @@ +/* + * mapreader.cpp + * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * Copyright 2010, Jeff Bland <jksb@member.fsf.org> + * Copyright 2010, Dennis Honeyman <arcticuno@gmail.com> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "mapreader.h" + +#include "compression.h" +#include "gidmapper.h" +#include "objectgroup.h" +#include "map.h" +#include "mapobject.h" +#include "tile.h" +#include "tilelayer.h" +#include "tileset.h" + +#include <QCoreApplication> +#include <QDebug> +#include <QDir> +#include <QFileInfo> +#include <QXmlStreamReader> + +using namespace Tiled; +using namespace Tiled::Internal; + +namespace Tiled { +namespace Internal { + +class MapReaderPrivate +{ + Q_DECLARE_TR_FUNCTIONS(MapReader) + +public: + MapReaderPrivate(MapReader *mapReader): + p(mapReader), + mMap(0), + mReadingExternalTileset(false) + {} + + Map *readMap(QIODevice *device, const QString &path); + Tileset *readTileset(QIODevice *device, const QString &path); + + bool openFile(QFile *file); + + QString errorString() const; + +private: + void readUnknownElement(); + + Map *readMap(); + + Tileset *readTileset(); + void readTilesetTile(Tileset *tileset); + void readTilesetImage(Tileset *tileset); + + TileLayer *readLayer(); + void readLayerData(TileLayer *tileLayer); + void decodeBinaryLayerData(TileLayer *tileLayer, + const QStringRef &text, + const QStringRef &compression); + void decodeCSVLayerData(TileLayer *tileLayer, const QString &text); + + /** + * Returns the cell for the given global tile ID. Errors are raised with + * the QXmlStreamReader. + * + * @param gid the global tile ID + * @return the cell data associated with the given global tile ID, or an + * empty cell if not found + */ + Cell cellForGid(uint gid); + + ObjectGroup *readObjectGroup(); + MapObject *readObject(); + QPolygonF readPolygon(); + + Properties readProperties(); + void readProperty(Properties *properties); + + MapReader *p; + + QString mError; + QString mPath; + Map *mMap; + GidMapper mGidMapper; + bool mReadingExternalTileset; + + QXmlStreamReader xml; +}; + +} // namespace Internal +} // namespace Tiled + +Map *MapReaderPrivate::readMap(QIODevice *device, const QString &path) +{ + mError.clear(); + mPath = path; + Map *map = 0; + + xml.setDevice(device); + + if (xml.readNextStartElement() && xml.name() == "map") { + map = readMap(); + } else { + xml.raiseError(tr("Not a map file.")); + } + + mGidMapper.clear(); + return map; +} + +Tileset *MapReaderPrivate::readTileset(QIODevice *device, const QString &path) +{ + mError.clear(); + mPath = path; + Tileset *tileset = 0; + mReadingExternalTileset = true; + + xml.setDevice(device); + + if (xml.readNextStartElement() && xml.name() == "tileset") + tileset = readTileset(); + else + xml.raiseError(tr("Not a tileset file.")); + + mReadingExternalTileset = false; + return tileset; +} + +QString MapReaderPrivate::errorString() const +{ + if (!mError.isEmpty()) { + return mError; + } else { + return tr("%3\n\nLine %1, column %2") + .arg(xml.lineNumber()) + .arg(xml.columnNumber()) + .arg(xml.errorString()); + } +} + +bool MapReaderPrivate::openFile(QFile *file) +{ + if (!file->exists()) { + mError = tr("File not found: %1").arg(file->fileName()); + return false; + } else if (!file->open(QFile::ReadOnly | QFile::Text)) { + mError = tr("Unable to read file: %1").arg(file->fileName()); + return false; + } + + return true; +} + +void MapReaderPrivate::readUnknownElement() +{ + qDebug() << "Unknown element (fixme):" << xml.name(); + xml.skipCurrentElement(); +} + +Map *MapReaderPrivate::readMap() +{ + Q_ASSERT(xml.isStartElement() && xml.name() == "map"); + + const QXmlStreamAttributes atts = xml.attributes(); + const int mapWidth = + atts.value(QLatin1String("width")).toString().toInt(); + const int mapHeight = + atts.value(QLatin1String("height")).toString().toInt(); + const int tileWidth = + atts.value(QLatin1String("tilewidth")).toString().toInt(); + const int tileHeight = + atts.value(QLatin1String("tileheight")).toString().toInt(); + + const QString orientationString = + atts.value(QLatin1String("orientation")).toString(); + const Map::Orientation orientation = + orientationFromString(orientationString); + + if (orientation == Map::Unknown) { + xml.raiseError(tr("Unsupported map orientation: \"%1\"") + .arg(orientationString)); + } + + mMap = new Map(orientation, mapWidth, mapHeight, tileWidth, tileHeight); + + while (xml.readNextStartElement()) { + if (xml.name() == "properties") + mMap->mergeProperties(readProperties()); + else if (xml.name() == "tileset") + mMap->addTileset(readTileset()); + else if (xml.name() == "layer") + mMap->addLayer(readLayer()); + else if (xml.name() == "objectgroup") + mMap->addLayer(readObjectGroup()); + else + readUnknownElement(); + } + + // Clean up in case of error + if (xml.hasError()) { + // The tilesets are not owned by the map + qDeleteAll(mMap->tilesets()); + + delete mMap; + mMap = 0; + } + + return mMap; +} + +Tileset *MapReaderPrivate::readTileset() +{ + Q_ASSERT(xml.isStartElement() && xml.name() == "tileset"); + + const QXmlStreamAttributes atts = xml.attributes(); + const QString source = atts.value(QLatin1String("source")).toString(); + const uint firstGid = + atts.value(QLatin1String("firstgid")).toString().toUInt(); + + Tileset *tileset = 0; + + if (source.isEmpty()) { // Not an external tileset + const QString name = + atts.value(QLatin1String("name")).toString(); + const int tileWidth = + atts.value(QLatin1String("tilewidth")).toString().toInt(); + const int tileHeight = + atts.value(QLatin1String("tileheight")).toString().toInt(); + const int tileSpacing = + atts.value(QLatin1String("spacing")).toString().toInt(); + const int margin = + atts.value(QLatin1String("margin")).toString().toInt(); + + if (tileWidth <= 0 || tileHeight <= 0 + || (firstGid == 0 && !mReadingExternalTileset)) { + xml.raiseError(tr("Invalid tileset parameters for tileset" + " '%1'").arg(name)); + } else { + tileset = new Tileset(name, tileWidth, tileHeight, + tileSpacing, margin); + + while (xml.readNextStartElement()) { + if (xml.name() == "tile") { + readTilesetTile(tileset); + } else if (xml.name() == "tileoffset") { + const QXmlStreamAttributes oa = xml.attributes(); + int x = oa.value(QLatin1String("x")).toString().toInt(); + int y = oa.value(QLatin1String("y")).toString().toInt(); + tileset->setTileOffset(QPoint(x, y)); + xml.skipCurrentElement(); + } else if (xml.name() == "properties") { + tileset->mergeProperties(readProperties()); + } else if (xml.name() == "image") { + readTilesetImage(tileset); + } else { + readUnknownElement(); + } + } + } + } else { // External tileset + const QString absoluteSource = p->resolveReference(source, mPath); + QString error; + tileset = p->readExternalTileset(absoluteSource, &error); + + if (!tileset) { + xml.raiseError(tr("Error while loading tileset '%1': %2") + .arg(absoluteSource, error)); + } + + xml.skipCurrentElement(); + } + + if (tileset && !mReadingExternalTileset) + mGidMapper.insert(firstGid, tileset); + + return tileset; +} + +void MapReaderPrivate::readTilesetTile(Tileset *tileset) +{ + Q_ASSERT(xml.isStartElement() && xml.name() == "tile"); + + const QXmlStreamAttributes atts = xml.attributes(); + const int id = atts.value(QLatin1String("id")).toString().toInt(); + + if (id < 0 || id >= tileset->tileCount()) { + xml.raiseError(tr("Invalid tile ID: %1").arg(id)); + return; + } + + // TODO: Add support for individual tiles (then it needs to be added here) + + while (xml.readNextStartElement()) { + if (xml.name() == "properties") { + Tile *tile = tileset->tileAt(id); + tile->mergeProperties(readProperties()); + } else { + readUnknownElement(); + } + } +} + +void MapReaderPrivate::readTilesetImage(Tileset *tileset) +{ + Q_ASSERT(xml.isStartElement() && xml.name() == "image"); + + const QXmlStreamAttributes atts = xml.attributes(); + QString source = atts.value(QLatin1String("source")).toString(); + QString trans = atts.value(QLatin1String("trans")).toString(); + + if (!trans.isEmpty()) { + if (!trans.startsWith(QLatin1Char('#'))) + trans.prepend(QLatin1Char('#')); + tileset->setTransparentColor(QColor(trans)); + } + + source = p->resolveReference(source, mPath); + + // Set the width that the tileset had when the map was saved + const int width = atts.value(QLatin1String("width")).toString().toInt(); + mGidMapper.setTilesetWidth(tileset, width); + + const QImage tilesetImage = p->readExternalImage(source); + if (!tileset->loadFromImage(tilesetImage, source)) + xml.raiseError(tr("Error loading tileset image:\n'%1'").arg(source)); + + xml.skipCurrentElement(); +} + +static void readLayerAttributes(Layer *layer, + const QXmlStreamAttributes &atts) +{ + const QStringRef opacityRef = atts.value(QLatin1String("opacity")); + const QStringRef visibleRef = atts.value(QLatin1String("visible")); + + bool ok; + const float opacity = opacityRef.toString().toFloat(&ok); + if (ok) + layer->setOpacity(opacity); + + const int visible = visibleRef.toString().toInt(&ok); + if (ok) + layer->setVisible(visible); +} + +TileLayer *MapReaderPrivate::readLayer() +{ + Q_ASSERT(xml.isStartElement() && xml.name() == "layer"); + + const QXmlStreamAttributes atts = xml.attributes(); + const QString name = atts.value(QLatin1String("name")).toString(); + const int x = atts.value(QLatin1String("x")).toString().toInt(); + const int y = atts.value(QLatin1String("y")).toString().toInt(); + const int width = atts.value(QLatin1String("width")).toString().toInt(); + const int height = atts.value(QLatin1String("height")).toString().toInt(); + + TileLayer *tileLayer = new TileLayer(name, x, y, width, height); + readLayerAttributes(tileLayer, atts); + + while (xml.readNextStartElement()) { + if (xml.name() == "properties") + tileLayer->mergeProperties(readProperties()); + else if (xml.name() == "data") + readLayerData(tileLayer); + else + readUnknownElement(); + } + + return tileLayer; +} + +void MapReaderPrivate::readLayerData(TileLayer *tileLayer) +{ + Q_ASSERT(xml.isStartElement() && xml.name() == "data"); + + const QXmlStreamAttributes atts = xml.attributes(); + QStringRef encoding = atts.value(QLatin1String("encoding")); + QStringRef compression = atts.value(QLatin1String("compression")); + + int x = 0; + int y = 0; + + while (xml.readNext() != QXmlStreamReader::Invalid) { + if (xml.isEndElement()) + break; + else if (xml.isStartElement()) { + if (xml.name() == QLatin1String("tile")) { + if (y >= tileLayer->height()) { + xml.raiseError(tr("Too many <tile> elements")); + continue; + } + + const QXmlStreamAttributes atts = xml.attributes(); + uint gid = atts.value(QLatin1String("gid")).toString().toUInt(); + tileLayer->setCell(x, y, cellForGid(gid)); + + x++; + if (x >= tileLayer->width()) { + x = 0; + y++; + } + + xml.skipCurrentElement(); + } else { + readUnknownElement(); + } + } else if (xml.isCharacters() && !xml.isWhitespace()) { + if (encoding == QLatin1String("base64")) { + decodeBinaryLayerData(tileLayer, + xml.text(), + compression); + } else if (encoding == QLatin1String("csv")) { + decodeCSVLayerData(tileLayer, xml.text().toString()); + } else { + xml.raiseError(tr("Unknown encoding: %1") + .arg(encoding.toString())); + continue; + } + } + } +} + +void MapReaderPrivate::decodeBinaryLayerData(TileLayer *tileLayer, + const QStringRef &text, + const QStringRef &compression) +{ +#if QT_VERSION < 0x040800 + const QString textData = QString::fromRawData(text.unicode(), text.size()); + const QByteArray latin1Text = textData.toLatin1(); +#else + const QByteArray latin1Text = text.toLatin1(); +#endif + QByteArray tileData = QByteArray::fromBase64(latin1Text); + const int size = (tileLayer->width() * tileLayer->height()) * 4; + + if (compression == QLatin1String("zlib") + || compression == QLatin1String("gzip")) { + tileData = decompress(tileData, size); + } else if (!compression.isEmpty()) { + xml.raiseError(tr("Compression method '%1' not supported") + .arg(compression.toString())); + return; + } + + if (size != tileData.length()) { + xml.raiseError(tr("Corrupt layer data for layer '%1'") + .arg(tileLayer->name())); + return; + } + + const unsigned char *data = + reinterpret_cast<const unsigned char*>(tileData.constData()); + int x = 0; + int y = 0; + + for (int i = 0; i < size - 3; i += 4) { + const uint gid = data[i] | + data[i + 1] << 8 | + data[i + 2] << 16 | + data[i + 3] << 24; + + tileLayer->setCell(x, y, cellForGid(gid)); + + x++; + if (x == tileLayer->width()) { + x = 0; + y++; + } + } +} + +void MapReaderPrivate::decodeCSVLayerData(TileLayer *tileLayer, const QString &text) +{ + QString trimText = text.trimmed(); + QStringList tiles = trimText.split(QLatin1Char(',')); + + if (tiles.length() != tileLayer->width() * tileLayer->height()) { + xml.raiseError(tr("Corrupt layer data for layer '%1'") + .arg(tileLayer->name())); + return; + } + + for (int y = 0; y < tileLayer->height(); y++) { + for (int x = 0; x < tileLayer->width(); x++) { + bool conversionOk; + const uint gid = tiles.at(y * tileLayer->width() + x) + .toUInt(&conversionOk); + if (!conversionOk) { + xml.raiseError( + tr("Unable to parse tile at (%1,%2) on layer '%3'") + .arg(x + 1).arg(y + 1).arg(tileLayer->name())); + return; + } + tileLayer->setCell(x, y, cellForGid(gid)); + } + } +} + +Cell MapReaderPrivate::cellForGid(uint gid) +{ + bool ok; + const Cell result = mGidMapper.gidToCell(gid, ok); + + if (!ok) { + if (mGidMapper.isEmpty()) + xml.raiseError(tr("Tile used but no tilesets specified")); + else + xml.raiseError(tr("Invalid tile: %1").arg(gid)); + } + + return result; +} + +ObjectGroup *MapReaderPrivate::readObjectGroup() +{ + Q_ASSERT(xml.isStartElement() && xml.name() == "objectgroup"); + + const QXmlStreamAttributes atts = xml.attributes(); + const QString name = atts.value(QLatin1String("name")).toString(); + const int x = atts.value(QLatin1String("x")).toString().toInt(); + const int y = atts.value(QLatin1String("y")).toString().toInt(); + const int width = atts.value(QLatin1String("width")).toString().toInt(); + const int height = atts.value(QLatin1String("height")).toString().toInt(); + + ObjectGroup *objectGroup = new ObjectGroup(name, x, y, width, height); + readLayerAttributes(objectGroup, atts); + + const QString color = atts.value(QLatin1String("color")).toString(); + if (!color.isEmpty()) + objectGroup->setColor(color); + + while (xml.readNextStartElement()) { + if (xml.name() == "object") + objectGroup->addObject(readObject()); + else if (xml.name() == "properties") + objectGroup->mergeProperties(readProperties()); + else + readUnknownElement(); + } + + return objectGroup; +} + +static QPointF pixelToTileCoordinates(Map *map, int x, int y) +{ + const int tileHeight = map->tileHeight(); + const int tileWidth = map->tileWidth(); + + if (map->orientation() == Map::Isometric) { + // Isometric needs special handling, since the pixel values are based + // solely on the tile height. + return QPointF((qreal) x / tileHeight, + (qreal) y / tileHeight); + } else { + return QPointF((qreal) x / tileWidth, + (qreal) y / tileHeight); + } +} + +MapObject *MapReaderPrivate::readObject() +{ + Q_ASSERT(xml.isStartElement() && xml.name() == "object"); + + const QXmlStreamAttributes atts = xml.attributes(); + const QString name = atts.value(QLatin1String("name")).toString(); + const uint gid = atts.value(QLatin1String("gid")).toString().toUInt(); + const int x = atts.value(QLatin1String("x")).toString().toInt(); + const int y = atts.value(QLatin1String("y")).toString().toInt(); + const int width = atts.value(QLatin1String("width")).toString().toInt(); + const int height = atts.value(QLatin1String("height")).toString().toInt(); + const QString type = atts.value(QLatin1String("type")).toString(); + + const QPointF pos = pixelToTileCoordinates(mMap, x, y); + const QPointF size = pixelToTileCoordinates(mMap, width, height); + + MapObject *object = new MapObject(name, type, pos, QSizeF(size.x(), + size.y())); + + if (gid) { + const Cell cell = cellForGid(gid); + object->setTile(cell.tile); + } + + while (xml.readNextStartElement()) { + if (xml.name() == "properties") { + object->mergeProperties(readProperties()); + } else if (xml.name() == "polygon") { + object->setPolygon(readPolygon()); + object->setShape(MapObject::Polygon); + } else if (xml.name() == "polyline") { + object->setPolygon(readPolygon()); + object->setShape(MapObject::Polyline); + } else { + readUnknownElement(); + } + } + + return object; +} + +QPolygonF MapReaderPrivate::readPolygon() +{ + Q_ASSERT(xml.isStartElement() && (xml.name() == "polygon" || + xml.name() == "polyline")); + + const QXmlStreamAttributes atts = xml.attributes(); + const QString points = atts.value(QLatin1String("points")).toString(); + const QStringList pointsList = points.split(QLatin1Char(' '), + QString::SkipEmptyParts); + + QPolygonF polygon; + bool ok = true; + + foreach (const QString &point, pointsList) { + const int commaPos = point.indexOf(QLatin1Char(',')); + if (commaPos == -1) { + ok = false; + break; + } + + const int x = point.left(commaPos).toInt(&ok); + if (!ok) + break; + const int y = point.mid(commaPos + 1).toInt(&ok); + if (!ok) + break; + + polygon.append(pixelToTileCoordinates(mMap, x, y)); + } + + if (!ok) + xml.raiseError(tr("Invalid points data for polygon")); + + xml.skipCurrentElement(); + return polygon; +} + +Properties MapReaderPrivate::readProperties() +{ + Q_ASSERT(xml.isStartElement() && xml.name() == "properties"); + + Properties properties; + + while (xml.readNextStartElement()) { + if (xml.name() == "property") + readProperty(&properties); + else + readUnknownElement(); + } + + return properties; +} + +void MapReaderPrivate::readProperty(Properties *properties) +{ + Q_ASSERT(xml.isStartElement() && xml.name() == "property"); + + const QXmlStreamAttributes atts = xml.attributes(); + QString propertyName = atts.value(QLatin1String("name")).toString(); + QString propertyValue = atts.value(QLatin1String("value")).toString(); + + while (xml.readNext() != QXmlStreamReader::Invalid) { + if (xml.isEndElement()) { + break; + } else if (xml.isCharacters() && !xml.isWhitespace()) { + if (propertyValue.isEmpty()) + propertyValue = xml.text().toString(); + } else if (xml.isStartElement()) { + readUnknownElement(); + } + } + + properties->insert(propertyName, propertyValue); +} + + +MapReader::MapReader() + : d(new MapReaderPrivate(this)) +{ +} + +MapReader::~MapReader() +{ + delete d; +} + +Map *MapReader::readMap(QIODevice *device, const QString &path) +{ + return d->readMap(device, path); +} + +Map *MapReader::readMap(const QString &fileName) +{ + QFile file(fileName); + if (!d->openFile(&file)) + return 0; + + return readMap(&file, QFileInfo(fileName).absolutePath()); +} + +Tileset *MapReader::readTileset(QIODevice *device, const QString &path) +{ + return d->readTileset(device, path); +} + +Tileset *MapReader::readTileset(const QString &fileName) +{ + QFile file(fileName); + if (!d->openFile(&file)) + return 0; + + Tileset *tileset = readTileset(&file, QFileInfo(fileName).absolutePath()); + if (tileset) + tileset->setFileName(fileName); + + return tileset; +} + +QString MapReader::errorString() const +{ + return d->errorString(); +} + +QString MapReader::resolveReference(const QString &reference, + const QString &mapPath) +{ + if (QDir::isRelativePath(reference)) + return mapPath + QLatin1Char('/') + reference; + else + return reference; +} + +QImage MapReader::readExternalImage(const QString &source) +{ + return QImage(source); +} + +Tileset *MapReader::readExternalTileset(const QString &source, + QString *error) +{ + MapReader reader; + Tileset *tileset = reader.readTileset(source); + if (!tileset) + *error = reader.errorString(); + return tileset; +} diff --git a/libtiled/mapreader.h b/libtiled/mapreader.h new file mode 100644 index 0000000..39db882 --- /dev/null +++ b/libtiled/mapreader.h @@ -0,0 +1,129 @@ +/* + * mapreader.h + * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MAPREADER_H +#define MAPREADER_H + +#include "tiled_global.h" + +#include <QImage> + +class QFile; + +namespace Tiled { + +class Map; +class Tileset; + +namespace Internal { +class MapReaderPrivate; +} + +/** + * A fast QXmlStreamReader based reader for the TMX and TSX formats. + * + * Can be subclassed when special handling of external images and tilesets is + * needed. + */ +class TILEDSHARED_EXPORT MapReader +{ +public: + MapReader(); + ~MapReader(); + + /** + * Reads a TMX map from the given \a device. Optionally a \a path can + * be given, which will be used to resolve relative references to external + * images and tilesets. + * + * Returns 0 and sets errorString() when reading failed. + * + * The caller takes ownership over the newly created map. + */ + Map *readMap(QIODevice *device, const QString &path = QString()); + + /** + * Reads a TMX map from the given \a fileName. + * \overload + */ + Map *readMap(const QString &fileName); + + /** + * Reads a TSX tileset from the given \a device. Optionally a \a path can + * be given, which will be used to resolve relative references to external + * images. + * + * Returns 0 and sets errorString() when reading failed. + * + * The caller takes ownership over the newly created tileset. + */ + Tileset *readTileset(QIODevice *device, const QString &path = QString()); + + /** + * Reads a TSX tileset from the given \a fileName. + * \overload + */ + Tileset *readTileset(const QString &fileName); + + /** + * Returns the error message for the last occurred error. + */ + QString errorString() const; + +protected: + /** + * Called for each \a reference to an external file. Should return the path + * to be used when loading this file. \a mapPath contains the path to the + * map or tileset that is currently being loaded. + */ + virtual QString resolveReference(const QString &reference, + const QString &mapPath); + + /** + * Called when an external image is encountered while a tileset is loaded. + */ + virtual QImage readExternalImage(const QString &source); + + /** + * Called when an external tileset is encountered while a map is loaded. + * The default implementation just calls readTileset() on a new MapReader. + * + * If an error occurred, the \a error parameter should be set to the error + * message. + */ + virtual Tileset *readExternalTileset(const QString &source, + QString *error); + +private: + friend class Internal::MapReaderPrivate; + Internal::MapReaderPrivate *d; +}; + +} // namespace Tiled + +#endif // MAPREADER_H diff --git a/libtiled/maprenderer.cpp b/libtiled/maprenderer.cpp new file mode 100644 index 0000000..719bd42 --- /dev/null +++ b/libtiled/maprenderer.cpp @@ -0,0 +1,46 @@ +/* + * maprenderer.cpp + * Copyright 2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * + * This file is part of Tiled. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "maprenderer.h" + +#include <QVector2D> + +using namespace Tiled; + +/** + * Converts a line running from \a start to \a end to a polygon which + * extends 5 pixels from the line in all directions. + */ +QPolygonF MapRenderer::lineToPolygon(const QPointF &start, const QPointF &end) +{ + QPointF direction = QVector2D(end - start).normalized().toPointF(); + QPointF perpendicular(-direction.y(), direction.x()); + + const qreal thickness = 5.0f; // 5 pixels on each side + direction *= thickness; + perpendicular *= thickness; + + QPolygonF polygon(4); + polygon[0] = start + perpendicular - direction; + polygon[1] = start - perpendicular - direction; + polygon[2] = end - perpendicular + direction; + polygon[3] = end + perpendicular + direction; + return polygon; +} diff --git a/libtiled/maprenderer.h b/libtiled/maprenderer.h new file mode 100644 index 0000000..0943df0 --- /dev/null +++ b/libtiled/maprenderer.h @@ -0,0 +1,152 @@ +/* + * maprenderer.h + * Copyright 2009-2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MAPRENDERER_H +#define MAPRENDERER_H + +#include "tiled_global.h" + +#include <QPainter> + +namespace Tiled { + +class Layer; +class Map; +class MapObject; +class TileLayer; + +/** + * This interface is used for rendering tile layers and retrieving associated + * metrics. The different implementations deal with different map + * orientations. + */ +class TILEDSHARED_EXPORT MapRenderer +{ +public: + MapRenderer(const Map *map) : mMap(map) {} + virtual ~MapRenderer() {} + + /** + * Returns the size in pixels of the map associated with this renderer. + */ + virtual QSize mapSize() const = 0; + + /** + * Returns the bounding rectangle in pixels of the given \a rect given in + * tile coordinates. + * + * This is useful for calculating the bounding rect of a tile layer or of + * a region of tiles that was changed. + */ + virtual QRect boundingRect(const QRect &rect) const = 0; + + /** + * Returns the bounding rectangle in pixels of the given \a object, as it + * would be drawn by drawMapObject(). + */ + virtual QRectF boundingRect(const MapObject *object) const = 0; + + /** + * Returns the shape in pixels of the given \a object. This is used for + * mouse interaction and should match the rendered object as closely as + * possible. + */ + virtual QPainterPath shape(const MapObject *object) const = 0; + + /** + * Draws the tile grid in the specified \a rect using the given + * \a painter. + */ + virtual void drawGrid(QPainter *painter, const QRectF &rect) const = 0; + + /** + * Draws the given \a layer using the given \a painter. + * + * Optionally, you can pass in the \a exposed rect (of pixels), so that + * only tiles that can be visible in this area will be drawn. + */ + virtual void drawTileLayer(QPainter *painter, const TileLayer *layer, + const QRectF &exposed = QRectF()) const = 0; + + /** + * Draws the tile selection given by \a region in the specified \a color. + * + * The implementation can be optimized by taking into account the + * \a exposed rectangle, to avoid drawing too much. + */ + virtual void drawTileSelection(QPainter *painter, + const QRegion ®ion, + const QColor &color, + const QRectF &exposed) const = 0; + + /** + * Draws the \a object in the given \a color using the \a painter. + */ + virtual void drawMapObject(QPainter *painter, + const MapObject *object, + const QColor &color) const = 0; + + /** + * Returns the tile coordinates matching the given pixel position. + */ + virtual QPointF pixelToTileCoords(qreal x, qreal y) const = 0; + + inline QPointF pixelToTileCoords(const QPointF &point) const + { return pixelToTileCoords(point.x(), point.y()); } + + /** + * Returns the pixel coordinates matching the given tile coordinates. + */ + virtual QPointF tileToPixelCoords(qreal x, qreal y) const = 0; + + inline QPointF tileToPixelCoords(const QPointF &point) const + { return tileToPixelCoords(point.x(), point.y()); } + + QPolygonF tileToPixelCoords(const QPolygonF &polygon) const + { + QPolygonF screenPolygon(polygon.size()); + for (int i = polygon.size() - 1; i >= 0; --i) + screenPolygon[i] = tileToPixelCoords(polygon[i]); + return screenPolygon; + } + + static QPolygonF lineToPolygon(const QPointF &start, const QPointF &end); + +protected: + /** + * Returns the map this renderer is associated with. + */ + const Map *map() const { return mMap; } + +private: + const Map *mMap; +}; + +} // namespace Tiled + +#endif // MAPRENDERER_H diff --git a/libtiled/mapwriter.cpp b/libtiled/mapwriter.cpp new file mode 100644 index 0000000..4242bfb --- /dev/null +++ b/libtiled/mapwriter.cpp @@ -0,0 +1,580 @@ +/* + * mapwriter.cpp + * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * Copyright 2010, Jeff Bland <jksb@member.fsf.org> + * Copyright 2010, Dennis Honeyman <arcticuno@gmail.com> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "mapwriter.h" + +#include "compression.h" +#include "gidmapper.h" +#include "map.h" +#include "mapobject.h" +#include "objectgroup.h" +#include "tile.h" +#include "tilelayer.h" +#include "tileset.h" + +#include <QCoreApplication> +#include <QDir> +#include <QXmlStreamWriter> + +using namespace Tiled; +using namespace Tiled::Internal; + +namespace Tiled { +namespace Internal { + +class MapWriterPrivate +{ + Q_DECLARE_TR_FUNCTIONS(MapReader) + +public: + MapWriterPrivate(); + + void writeMap(const Map *map, QIODevice *device, + const QString &path); + + void writeTileset(const Tileset *tileset, QIODevice *device, + const QString &path); + + bool openFile(QFile *file); + + QString mError; + MapWriter::LayerDataFormat mLayerDataFormat; + bool mDtdEnabled; + +private: + void writeMap(QXmlStreamWriter &w, const Map *map); + void writeTileset(QXmlStreamWriter &w, const Tileset *tileset, + uint firstGid); + void writeTileLayer(QXmlStreamWriter &w, const TileLayer *tileLayer); + void writeLayerAttributes(QXmlStreamWriter &w, const Layer *layer); + void writeObjectGroup(QXmlStreamWriter &w, const ObjectGroup *objectGroup); + void writeObject(QXmlStreamWriter &w, const MapObject *mapObject); + void writeProperties(QXmlStreamWriter &w, + const Properties &properties); + + QDir mMapDir; // The directory in which the map is being saved + GidMapper mGidMapper; + bool mUseAbsolutePaths; +}; + +} // namespace Internal +} // namespace Tiled + + +MapWriterPrivate::MapWriterPrivate() + : mLayerDataFormat(MapWriter::Base64Gzip) + , mDtdEnabled(false) + , mUseAbsolutePaths(false) +{ +} + +bool MapWriterPrivate::openFile(QFile *file) +{ + if (!file->open(QIODevice::WriteOnly)) { + mError = tr("Could not open file for writing."); + return false; + } + + return true; +} + +static QXmlStreamWriter *createWriter(QIODevice *device) +{ + QXmlStreamWriter *writer = new QXmlStreamWriter(device); + writer->setAutoFormatting(true); + writer->setAutoFormattingIndent(1); + return writer; +} + +void MapWriterPrivate::writeMap(const Map *map, QIODevice *device, + const QString &path) +{ + mMapDir = QDir(path); + mUseAbsolutePaths = path.isEmpty(); + + QXmlStreamWriter *writer = createWriter(device); + writer->writeStartDocument(); + + if (mDtdEnabled) { + writer->writeDTD(QLatin1String("<!DOCTYPE map SYSTEM \"" + "http://mapeditor.org/dtd/1.0/" + "map.dtd\">")); + } + + writeMap(*writer, map); + writer->writeEndDocument(); + delete writer; +} + +void MapWriterPrivate::writeTileset(const Tileset *tileset, QIODevice *device, + const QString &path) +{ + mMapDir = QDir(path); + mUseAbsolutePaths = path.isEmpty(); + + QXmlStreamWriter *writer = createWriter(device); + writer->writeStartDocument(); + + if (mDtdEnabled) { + writer->writeDTD(QLatin1String("<!DOCTYPE tileset SYSTEM \"" + "http://mapeditor.org/dtd/1.0/" + "map.dtd\">")); + } + + writeTileset(*writer, tileset, 0); + writer->writeEndDocument(); + delete writer; +} + +void MapWriterPrivate::writeMap(QXmlStreamWriter &w, const Map *map) +{ + w.writeStartElement(QLatin1String("map")); + + const QString orientation = orientationToString(map->orientation()); + + w.writeAttribute(QLatin1String("version"), QLatin1String("1.0")); + w.writeAttribute(QLatin1String("orientation"), orientation); + w.writeAttribute(QLatin1String("width"), QString::number(map->width())); + w.writeAttribute(QLatin1String("height"), QString::number(map->height())); + w.writeAttribute(QLatin1String("tilewidth"), + QString::number(map->tileWidth())); + w.writeAttribute(QLatin1String("tileheight"), + QString::number(map->tileHeight())); + + writeProperties(w, map->properties()); + + mGidMapper.clear(); + uint firstGid = 1; + foreach (Tileset *tileset, map->tilesets()) { + writeTileset(w, tileset, firstGid); + mGidMapper.insert(firstGid, tileset); + firstGid += tileset->tileCount(); + } + + foreach (const Layer *layer, map->layers()) { + if (dynamic_cast<const TileLayer*>(layer) != 0) + writeTileLayer(w, static_cast<const TileLayer*>(layer)); + else if (dynamic_cast<const ObjectGroup*>(layer) != 0) + writeObjectGroup(w, static_cast<const ObjectGroup*>(layer)); + } + + w.writeEndElement(); +} + +void MapWriterPrivate::writeTileset(QXmlStreamWriter &w, const Tileset *tileset, + uint firstGid) +{ + w.writeStartElement(QLatin1String("tileset")); + if (firstGid > 0) + w.writeAttribute(QLatin1String("firstgid"), QString::number(firstGid)); + + const QString &fileName = tileset->fileName(); + if (!fileName.isEmpty()) { + QString source = fileName; + if (!mUseAbsolutePaths) + source = mMapDir.relativeFilePath(source); + w.writeAttribute(QLatin1String("source"), source); + + // Tileset is external, so no need to write any of the stuff below + w.writeEndElement(); + return; + } + + w.writeAttribute(QLatin1String("name"), tileset->name()); + w.writeAttribute(QLatin1String("tilewidth"), + QString::number(tileset->tileWidth())); + w.writeAttribute(QLatin1String("tileheight"), + QString::number(tileset->tileHeight())); + const int tileSpacing = tileset->tileSpacing(); + const int margin = tileset->margin(); + if (tileSpacing != 0) + w.writeAttribute(QLatin1String("spacing"), + QString::number(tileSpacing)); + if (margin != 0) + w.writeAttribute(QLatin1String("margin"), QString::number(margin)); + + const QPoint offset = tileset->tileOffset(); + if (!offset.isNull()) { + w.writeStartElement(QLatin1String("tileoffset")); + w.writeAttribute(QLatin1String("x"), QString::number(offset.x())); + w.writeAttribute(QLatin1String("y"), QString::number(offset.y())); + w.writeEndElement(); + } + + // Write the tileset properties + writeProperties(w, tileset->properties()); + + // Write the image element + const QString &imageSource = tileset->imageSource(); + if (!imageSource.isEmpty()) { + w.writeStartElement(QLatin1String("image")); + QString source = imageSource; + if (!mUseAbsolutePaths) + source = mMapDir.relativeFilePath(source); + w.writeAttribute(QLatin1String("source"), source); + + const QColor transColor = tileset->transparentColor(); + if (transColor.isValid()) + w.writeAttribute(QLatin1String("trans"), transColor.name().mid(1)); + + if (tileset->imageWidth() > 0) + w.writeAttribute(QLatin1String("width"), + QString::number(tileset->imageWidth())); + if (tileset->imageHeight() > 0) + w.writeAttribute(QLatin1String("height"), + QString::number(tileset->imageHeight())); + + w.writeEndElement(); + } + + // Write the properties for those tiles that have them + for (int i = 0; i < tileset->tileCount(); ++i) { + const Tile *tile = tileset->tileAt(i); + const Properties properties = tile->properties(); + if (!properties.isEmpty()) { + w.writeStartElement(QLatin1String("tile")); + w.writeAttribute(QLatin1String("id"), QString::number(i)); + writeProperties(w, properties); + w.writeEndElement(); + } + } + + w.writeEndElement(); +} + +void MapWriterPrivate::writeTileLayer(QXmlStreamWriter &w, + const TileLayer *tileLayer) +{ + w.writeStartElement(QLatin1String("layer")); + writeLayerAttributes(w, tileLayer); + writeProperties(w, tileLayer->properties()); + + QString encoding; + QString compression; + + if (mLayerDataFormat == MapWriter::Base64 + || mLayerDataFormat == MapWriter::Base64Gzip + || mLayerDataFormat == MapWriter::Base64Zlib) { + + encoding = QLatin1String("base64"); + + if (mLayerDataFormat == MapWriter::Base64Gzip) + compression = QLatin1String("gzip"); + else if (mLayerDataFormat == MapWriter::Base64Zlib) + compression = QLatin1String("zlib"); + + } else if (mLayerDataFormat == MapWriter::CSV) + encoding = QLatin1String("csv"); + + w.writeStartElement(QLatin1String("data")); + if (!encoding.isEmpty()) + w.writeAttribute(QLatin1String("encoding"), encoding); + if (!compression.isEmpty()) + w.writeAttribute(QLatin1String("compression"), compression); + + if (mLayerDataFormat == MapWriter::XML) { + for (int y = 0; y < tileLayer->height(); ++y) { + for (int x = 0; x < tileLayer->width(); ++x) { + const uint gid = mGidMapper.cellToGid(tileLayer->cellAt(x, y)); + w.writeStartElement(QLatin1String("tile")); + w.writeAttribute(QLatin1String("gid"), QString::number(gid)); + w.writeEndElement(); + } + } + } else if (mLayerDataFormat == MapWriter::CSV) { + QString tileData; + + for (int y = 0; y < tileLayer->height(); ++y) { + for (int x = 0; x < tileLayer->width(); ++x) { + const uint gid = mGidMapper.cellToGid(tileLayer->cellAt(x, y)); + tileData.append(QString::number(gid)); + if (x != tileLayer->width() - 1 + || y != tileLayer->height() - 1) + tileData.append(QLatin1String(",")); + } + tileData.append(QLatin1String("\n")); + } + + w.writeCharacters(QLatin1String("\n")); + w.writeCharacters(tileData); + } else { + QByteArray tileData; + tileData.reserve(tileLayer->height() * tileLayer->width() * 4); + + for (int y = 0; y < tileLayer->height(); ++y) { + for (int x = 0; x < tileLayer->width(); ++x) { + const uint gid = mGidMapper.cellToGid(tileLayer->cellAt(x, y)); + tileData.append((char) (gid)); + tileData.append((char) (gid >> 8)); + tileData.append((char) (gid >> 16)); + tileData.append((char) (gid >> 24)); + } + } + + if (mLayerDataFormat == MapWriter::Base64Gzip) + tileData = compress(tileData, Gzip); + else if (mLayerDataFormat == MapWriter::Base64Zlib) + tileData = compress(tileData, Zlib); + + w.writeCharacters(QLatin1String("\n ")); + w.writeCharacters(QString::fromLatin1(tileData.toBase64())); + w.writeCharacters(QLatin1String("\n ")); + } + + w.writeEndElement(); // </data> + w.writeEndElement(); // </layer> +} + +void MapWriterPrivate::writeLayerAttributes(QXmlStreamWriter &w, + const Layer *layer) +{ + w.writeAttribute(QLatin1String("name"), layer->name()); + w.writeAttribute(QLatin1String("width"), QString::number(layer->width())); + w.writeAttribute(QLatin1String("height"), + QString::number(layer->height())); + const int x = layer->x(); + const int y = layer->y(); + const qreal opacity = layer->opacity(); + if (x != 0) + w.writeAttribute(QLatin1String("x"), QString::number(x)); + if (y != 0) + w.writeAttribute(QLatin1String("y"), QString::number(y)); + if (!layer->isVisible()) + w.writeAttribute(QLatin1String("visible"), QLatin1String("0")); + if (opacity != qreal(1)) + w.writeAttribute(QLatin1String("opacity"), QString::number(opacity)); +} + +void MapWriterPrivate::writeObjectGroup(QXmlStreamWriter &w, + const ObjectGroup *objectGroup) +{ + w.writeStartElement(QLatin1String("objectgroup")); + + if (objectGroup->color().isValid()) + w.writeAttribute(QLatin1String("color"), + objectGroup->color().name()); + + writeLayerAttributes(w, objectGroup); + writeProperties(w, objectGroup->properties()); + + foreach (const MapObject *mapObject, objectGroup->objects()) + writeObject(w, mapObject); + + w.writeEndElement(); +} + +class TileToPixelCoordinates +{ +public: + TileToPixelCoordinates(Map *map) + { + if (map->orientation() == Map::Isometric) { + // Isometric needs special handling, since the pixel values are + // based solely on the tile height. + mMultiplierX = map->tileHeight(); + mMultiplierY = map->tileHeight(); + } else { + mMultiplierX = map->tileWidth(); + mMultiplierY = map->tileHeight(); + } + } + + QPoint operator() (qreal x, qreal y) const + { + return QPoint(qRound(x * mMultiplierX), + qRound(y * mMultiplierY)); + } + +private: + int mMultiplierX; + int mMultiplierY; +}; + +void MapWriterPrivate::writeObject(QXmlStreamWriter &w, + const MapObject *mapObject) +{ + w.writeStartElement(QLatin1String("object")); + const QString &name = mapObject->name(); + const QString &type = mapObject->type(); + if (!name.isEmpty()) + w.writeAttribute(QLatin1String("name"), name); + if (!type.isEmpty()) + w.writeAttribute(QLatin1String("type"), type); + + if (mapObject->tile()) { + const uint gid = mGidMapper.cellToGid(Cell(mapObject->tile())); + w.writeAttribute(QLatin1String("gid"), QString::number(gid)); + } + + // Convert from tile to pixel coordinates + const ObjectGroup *objectGroup = mapObject->objectGroup(); + const TileToPixelCoordinates toPixel(objectGroup->map()); + + QPoint pos = toPixel(mapObject->x(), mapObject->y()); + QPoint size = toPixel(mapObject->width(), mapObject->height()); + + w.writeAttribute(QLatin1String("x"), QString::number(pos.x())); + w.writeAttribute(QLatin1String("y"), QString::number(pos.y())); + + if (size.x() != 0) + w.writeAttribute(QLatin1String("width"), QString::number(size.x())); + if (size.y() != 0) + w.writeAttribute(QLatin1String("height"), QString::number(size.y())); + + writeProperties(w, mapObject->properties()); + + const QPolygonF &polygon = mapObject->polygon(); + if (!polygon.isEmpty()) { + if (mapObject->shape() == MapObject::Polygon) + w.writeStartElement(QLatin1String("polygon")); + else + w.writeStartElement(QLatin1String("polyline")); + + QString points; + foreach (const QPointF &point, polygon) { + const QPoint pos = toPixel(point.x(), point.y()); + points.append(QString::number(pos.x())); + points.append(QLatin1Char(',')); + points.append(QString::number(pos.y())); + points.append(QLatin1Char(' ')); + } + points.chop(1); + w.writeAttribute(QLatin1String("points"), points); + w.writeEndElement(); + } + + w.writeEndElement(); +} + +void MapWriterPrivate::writeProperties(QXmlStreamWriter &w, + const Properties &properties) +{ + if (properties.isEmpty()) + return; + + w.writeStartElement(QLatin1String("properties")); + + Properties::const_iterator it = properties.constBegin(); + Properties::const_iterator it_end = properties.constEnd(); + for (; it != it_end; ++it) { + w.writeStartElement(QLatin1String("property")); + w.writeAttribute(QLatin1String("name"), it.key()); + + const QString &value = it.value(); + if (value.contains(QLatin1Char('\n'))) { + w.writeCharacters(value); + } else { + w.writeAttribute(QLatin1String("value"), it.value()); + } + w.writeEndElement(); + } + + w.writeEndElement(); +} + + +MapWriter::MapWriter() + : d(new MapWriterPrivate) +{ +} + +MapWriter::~MapWriter() +{ + delete d; +} + +void MapWriter::writeMap(const Map *map, QIODevice *device, + const QString &path) +{ + d->writeMap(map, device, path); +} + +bool MapWriter::writeMap(const Map *map, const QString &fileName) +{ + QFile file(fileName); + if (!d->openFile(&file)) + return false; + + writeMap(map, &file, QFileInfo(fileName).absolutePath()); + + if (file.error() != QFile::NoError) { + d->mError = file.errorString(); + return false; + } + + return true; +} + +void MapWriter::writeTileset(const Tileset *tileset, QIODevice *device, + const QString &path) +{ + d->writeTileset(tileset, device, path); +} + +bool MapWriter::writeTileset(const Tileset *tileset, const QString &fileName) +{ + QFile file(fileName); + if (!d->openFile(&file)) + return false; + + writeTileset(tileset, &file, QFileInfo(fileName).absolutePath()); + + if (file.error() != QFile::NoError) { + d->mError = file.errorString(); + return false; + } + + return true; +} + +QString MapWriter::errorString() const +{ + return d->mError; +} + +void MapWriter::setLayerDataFormat(MapWriter::LayerDataFormat format) +{ + d->mLayerDataFormat = format; +} + +MapWriter::LayerDataFormat MapWriter::layerDataFormat() const +{ + return d->mLayerDataFormat; +} + +void MapWriter::setDtdEnabled(bool enabled) +{ + d->mDtdEnabled = enabled; +} + +bool MapWriter::isDtdEnabled() const +{ + return d->mDtdEnabled; +} diff --git a/libtiled/mapwriter.h b/libtiled/mapwriter.h new file mode 100644 index 0000000..6e346c8 --- /dev/null +++ b/libtiled/mapwriter.h @@ -0,0 +1,129 @@ +/* + * mapwriter.h + * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * Copyright 2010, Dennis Honeyman <arcticuno@gmail.com> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MAPWRITER_H +#define MAPWRITER_H + +#include "tiled_global.h" + +#include <QString> + +class QIODevice; + +namespace Tiled { + +class Map; +class Tileset; + +namespace Internal { +class MapWriterPrivate; +} + +/** + * A QXmlStreamWriter based writer for the TMX and TSX formats. + */ +class TILEDSHARED_EXPORT MapWriter +{ +public: + MapWriter(); + ~MapWriter(); + + /** + * Writes a TMX map to the given \a device. Optionally a \a path can + * be given, which will be used to create relative references to external + * images and tilesets. + * + * Error checking will need to be done on the \a device after calling this + * function. + */ + void writeMap(const Map *map, QIODevice *device, + const QString &path = QString()); + + /** + * Writes a TMX map to the given \a fileName. + * + * Returns false and sets errorString() when reading failed. + * \overload + */ + bool writeMap(const Map *map, const QString &fileName); + + /** + * Writes a TSX tileset to the given \a device. Optionally a \a path can + * be given, which will be used to create relative references to external + * images. + * + * Error checking will need to be done on the \a device after calling this + * function. + */ + void writeTileset(const Tileset *tileset, QIODevice *device, + const QString &path = QString()); + + /** + * Writes a TSX tileset to the given \a fileName. + * + * Returns false and sets errorString() when reading failed. + * \overload + */ + bool writeTileset(const Tileset *tileset, const QString &fileName); + + /** + * Returns the error message for the last occurred error. + */ + QString errorString() const; + + /** + * The different formats in which the tile layer data can be stored. + */ + enum LayerDataFormat { + XML = 0, + Base64 = 1, + Base64Gzip = 2, + Base64Zlib = 3, + CSV = 4 + }; + + /** + * Sets the format in which the tile layer data is stored. + */ + void setLayerDataFormat(LayerDataFormat format); + LayerDataFormat layerDataFormat() const; + + /** + * Sets whether the DTD reference is written when saving the map. + */ + void setDtdEnabled(bool enabled); + bool isDtdEnabled() const; + +private: + Internal::MapWriterPrivate *d; +}; + +} // namespace Tiled + +#endif // MAPWRITER_H diff --git a/libtiled/object.h b/libtiled/object.h new file mode 100644 index 0000000..3a1d9fd --- /dev/null +++ b/libtiled/object.h @@ -0,0 +1,85 @@ +/* + * object.h + * Copyright 2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef OBJECT_H +#define OBJECT_H + +#include "properties.h" + +namespace Tiled { + +/** + * The base class for anything that can hold properties. + */ +class TILEDSHARED_EXPORT Object +{ +public: + /** + * Virtual destructor. + */ + virtual ~Object() {} + + /** + * Returns the properties of this object. + */ + const Properties &properties() const { return mProperties; } + + /** + * Replaces all existing properties with a new set of properties. + */ + void setProperties(const Properties &properties) + { mProperties = properties; } + + /** + * Merges \a properties with the existing properties. Properties with the + * same name will be overridden. + * + * \sa Properties::merge + */ + void mergeProperties(const Properties &properties) + { mProperties.merge(properties); } + + /** + * Returns the value of the object's \a name property. + */ + QString property(const QString &name) const + { return mProperties.value(name); } + + /** + * Sets the value of the object's \a name property to \a value. + */ + void setProperty(const QString &name, const QString &value) + { mProperties.insert(name, value); } + +private: + Properties mProperties; +}; + +} // namespace Tiled + +#endif // OBJECT_H diff --git a/libtiled/objectgroup.cpp b/libtiled/objectgroup.cpp new file mode 100644 index 0000000..d0adc60 --- /dev/null +++ b/libtiled/objectgroup.cpp @@ -0,0 +1,198 @@ +/* + * objectgroup.cpp + * Copyright 2008, Roderic Morris <roderic@ccs.neu.edu> + * Copyright 2008-2009, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * Copyright 2009-2010, Jeff Bland <jksb@member.fsf.org> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "objectgroup.h" + +#include "map.h" +#include "mapobject.h" +#include "tile.h" +#include "tileset.h" + +using namespace Tiled; + +ObjectGroup::ObjectGroup() + : Layer(QString(), 0, 0, 0, 0) +{ +} + +ObjectGroup::ObjectGroup(const QString &name, + int x, int y, int width, int height) + : Layer(name, x, y, width, height) +{ +} + +ObjectGroup::~ObjectGroup() +{ + qDeleteAll(mObjects); +} + +void ObjectGroup::addObject(MapObject *object) +{ + mObjects.append(object); + object->setObjectGroup(this); +} + +void ObjectGroup::insertObject(int index, MapObject *object) +{ + mObjects.insert(index, object); + object->setObjectGroup(this); +} + +int ObjectGroup::removeObject(MapObject *object) +{ + const int index = mObjects.indexOf(object); + Q_ASSERT(index != -1); + + mObjects.removeAt(index); + object->setObjectGroup(0); + return index; +} + +QRectF ObjectGroup::objectsBoundingRect() const +{ + QRectF boundingRect; + foreach (const MapObject *object, mObjects) + boundingRect = boundingRect.united(object->bounds()); + return boundingRect; +} + +QSet<Tileset*> ObjectGroup::usedTilesets() const +{ + QSet<Tileset*> tilesets; + + foreach (const MapObject *object, mObjects) + if (const Tile *tile = object->tile()) + tilesets.insert(tile->tileset()); + + return tilesets; +} + +bool ObjectGroup::referencesTileset(const Tileset *tileset) const +{ + foreach (const MapObject *object, mObjects) { + const Tile *tile = object->tile(); + if (tile && tile->tileset() == tileset) + return true; + } + + return false; +} + +void ObjectGroup::replaceReferencesToTileset(Tileset *oldTileset, + Tileset *newTileset) +{ + foreach (MapObject *object, mObjects) { + const Tile *tile = object->tile(); + if (tile && tile->tileset() == oldTileset) + object->setTile(newTileset->tileAt(tile->id())); + } +} + +void ObjectGroup::resize(const QSize &size, const QPoint &offset) +{ + Layer::resize(size, offset); + + foreach (MapObject *object, mObjects) { + QPointF pos = object->position(); + pos.rx() += offset.x(); + pos.ry() += offset.y(); + object->setPosition(pos); + } +} + +void ObjectGroup::offset(const QPoint &offset, + const QRect &bounds, + bool wrapX, bool wrapY) +{ + foreach (MapObject *object, mObjects) { + const QRectF objectBounds = object->bounds(); + if (!QRectF(bounds).contains(objectBounds.center())) + continue; + + QPointF newPos(objectBounds.left() + offset.x(), + objectBounds.top () + offset.y()); + + if (wrapX && bounds.width() > 0) { + while (newPos.x() + objectBounds.width() / 2 + < qreal(bounds.left())) + newPos.rx() += qreal(bounds.width()); + while (newPos.x() + objectBounds.width() / 2 + > qreal(bounds.left() + bounds.width())) + newPos.rx() -= qreal(bounds.width()); + } + + if (wrapY && bounds.height() > 0) { + while (newPos.y() + objectBounds.height() / 2 + < qreal(bounds.top())) + newPos.ry() += qreal(bounds.height()); + while (newPos.y() + objectBounds.height() / 2 + > qreal(bounds.top() + bounds.height())) + newPos.ry() -= qreal(bounds.height()); + } + + object->setPosition(newPos); + } +} + +bool ObjectGroup::canMergeWith(Layer *other) const +{ + return dynamic_cast<ObjectGroup*>(other) != 0; +} + +Layer *ObjectGroup::mergedWith(Layer *other) const +{ + Q_ASSERT(canMergeWith(other)); + + const ObjectGroup *og = static_cast<ObjectGroup*>(other); + + ObjectGroup *merged = static_cast<ObjectGroup*>(clone()); + foreach (const MapObject *mapObject, og->objects()) + merged->addObject(mapObject->clone()); + return merged; +} + +/** + * Returns a duplicate of this ObjectGroup. + * + * \sa Layer::clone() + */ +Layer *ObjectGroup::clone() const +{ + return initializeClone(new ObjectGroup(mName, mX, mY, mWidth, mHeight)); +} + +ObjectGroup *ObjectGroup::initializeClone(ObjectGroup *clone) const +{ + Layer::initializeClone(clone); + foreach (const MapObject *object, mObjects) + clone->addObject(object->clone()); + clone->setColor(mColor); + return clone; +} diff --git a/libtiled/objectgroup.h b/libtiled/objectgroup.h new file mode 100644 index 0000000..423a5d7 --- /dev/null +++ b/libtiled/objectgroup.h @@ -0,0 +1,164 @@ +/* + * objectgroup.h + * Copyright 2008, Roderic Morris <roderic@ccs.neu.edu> + * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * Copyright 2009-2010, Jeff Bland <jksb@member.fsf.org> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef OBJECTGROUP_H +#define OBJECTGROUP_H + +#include "tiled_global.h" + +#include "layer.h" + +#include <QList> +#include <QColor> + +namespace Tiled { + +class MapObject; + +/** + * A group of objects on a map. + */ +class TILEDSHARED_EXPORT ObjectGroup : public Layer +{ +public: + /** + * Default constructor. + */ + ObjectGroup(); + + /** + * Constructor with some parameters. + */ + ObjectGroup(const QString &name, int x, int y, int width, int height); + + /** + * Destructor. + */ + ~ObjectGroup(); + + /** + * Returns a pointer to the list of objects in this object group. + */ + const QList<MapObject*> &objects() const { return mObjects; } + + /** + * Returns the number of objects in this object group. + */ + int objectCount() const { return mObjects.size(); } + + /** + * Adds an object to this object group. + */ + void addObject(MapObject *object); + + /** + * Inserts an object at the specified index. This is only used for undoing + * the removal of an object at the moment, to make sure not to change the + * saved order of the objects. + */ + void insertObject(int index, MapObject *object); + + /** + * Removes an object from this object group. Ownership of the object is + * transferred to the caller. + * + * @return the index at which the specified object was removed + */ + int removeObject(MapObject *object); + + /** + * Returns the bounding rect around all objects in this object group. + */ + QRectF objectsBoundingRect() const; + + /** + * Computes and returns the set of tilesets used by this object group. + */ + QSet<Tileset*> usedTilesets() const; + + /** + * Returns whether any tile objects in this object group reference tiles + * in the given tileset. + */ + bool referencesTileset(const Tileset *tileset) const; + + /** + * Replaces all references to tiles from \a oldTileset with tiles from + * \a newTileset. + */ + void replaceReferencesToTileset(Tileset *oldTileset, Tileset *newTileset); + + /** + * Resizes this object group to \a size, while shifting all objects by + * \a offset tiles. + * + * \sa Layer::resize() + */ + virtual void resize(const QSize &size, const QPoint &offset); + + /** + * Offsets all objects within the group, and optionally wraps them. The + * object's center must be within \a bounds, and wrapping occurs if the + * displaced center is out of the bounds. + * + * \sa Layer::offset() + */ + virtual void offset(const QPoint &offset, const QRect &bounds, + bool wrapX, bool wrapY); + + bool canMergeWith(Layer *other) const; + Layer *mergedWith(Layer *other) const; + + /** + * Returns the color of the object group, or an invalid color if no color + * is set. + */ + const QColor &color() const { return mColor; } + + /** + * Sets the display color of the object group. + */ + void setColor(const QColor &color) { mColor = color; } + + Layer *clone() const; + + virtual ObjectGroup *asObjectGroup() { return this; } + +protected: + ObjectGroup *initializeClone(ObjectGroup *clone) const; + +private: + QList<MapObject*> mObjects; + QColor mColor; +}; + +} // namespace Tiled + +#endif // OBJECTGROUP_H diff --git a/libtiled/orthogonalrenderer.cpp b/libtiled/orthogonalrenderer.cpp new file mode 100644 index 0000000..6f82590 --- /dev/null +++ b/libtiled/orthogonalrenderer.cpp @@ -0,0 +1,372 @@ +/* + * orthogonalrenderer.cpp + * Copyright 2009-2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "orthogonalrenderer.h" + +#include "map.h" +#include "mapobject.h" +#include "tile.h" +#include "tilelayer.h" +#include "tileset.h" + +#include <cmath> + +using namespace Tiled; + +QSize OrthogonalRenderer::mapSize() const +{ + return QSize(map()->width() * map()->tileWidth(), + map()->height() * map()->tileHeight()); +} + +QRect OrthogonalRenderer::boundingRect(const QRect &rect) const +{ + const int tileWidth = map()->tileWidth(); + const int tileHeight = map()->tileHeight(); + + return QRect(rect.x() * tileWidth, + rect.y() * tileHeight, + rect.width() * tileWidth, + rect.height() * tileHeight); +} + +QRectF OrthogonalRenderer::boundingRect(const MapObject *object) const +{ + const QRectF bounds = object->bounds(); + const QRectF rect(tileToPixelCoords(bounds.topLeft()), + tileToPixelCoords(bounds.bottomRight())); + + QRectF boundingRect; + + if (object->tile()) { + const QPointF bottomLeft = rect.topLeft(); + const QPixmap &img = object->tile()->image(); + boundingRect = QRectF(bottomLeft.x(), + bottomLeft.y() - img.height(), + img.width(), + img.height()).adjusted(-1, -1, 1, 1); + } else { + // The -2 and +3 are to account for the pen width and shadow + switch (object->shape()) { + case MapObject::Rectangle: + if (rect.isNull()) { + boundingRect = rect.adjusted(-10 - 2, -10 - 2, 10 + 3, 10 + 3); + } else { + const int nameHeight = object->name().isEmpty() ? 0 : 15; + boundingRect = rect.adjusted(-2, -nameHeight - 2, 3, 3); + } + break; + + case MapObject::Polygon: + case MapObject::Polyline: { + const QPointF &pos = object->position(); + const QPolygonF polygon = object->polygon().translated(pos); + QPolygonF screenPolygon = tileToPixelCoords(polygon); + boundingRect = screenPolygon.boundingRect().adjusted(-2, -2, 3, 3); + break; + } + } + } + + return boundingRect; +} + +QPainterPath OrthogonalRenderer::shape(const MapObject *object) const +{ + QPainterPath path; + + if (object->tile()) { + path.addRect(boundingRect(object)); + } else { + switch (object->shape()) { + case MapObject::Rectangle: { + const QRectF bounds = object->bounds(); + const QRectF rect(tileToPixelCoords(bounds.topLeft()), + tileToPixelCoords(bounds.bottomRight())); + + if (rect.isNull()) { + path.addEllipse(rect.topLeft(), 20, 20); + } else { + path.addRoundedRect(rect, 10, 10); + } + break; + } + case MapObject::Polygon: + case MapObject::Polyline: { + const QPointF &pos = object->position(); + const QPolygonF polygon = object->polygon().translated(pos); + const QPolygonF screenPolygon = tileToPixelCoords(polygon); + if (object->shape() == MapObject::Polygon) { + path.addPolygon(screenPolygon); + } else { + for (int i = 1; i < screenPolygon.size(); ++i) { + path.addPolygon(lineToPolygon(screenPolygon[i - 1], + screenPolygon[i])); + } + path.setFillRule(Qt::WindingFill); + } + break; + } + } + } + + return path; +} + +void OrthogonalRenderer::drawGrid(QPainter *painter, const QRectF &rect) const +{ + const int tileWidth = map()->tileWidth(); + const int tileHeight = map()->tileHeight(); + + if (tileWidth <= 0 || tileHeight <= 0) + return; + + const int startX = qMax(0, (int) (rect.x() / tileWidth) * tileWidth); + const int startY = qMax(0, (int) (rect.y() / tileHeight) * tileHeight); + const int endX = qMin((int) std::ceil(rect.right()), + map()->width() * tileWidth + 1); + const int endY = qMin((int) std::ceil(rect.bottom()), + map()->height() * tileHeight + 1); + + QColor gridColor(Qt::black); + gridColor.setAlpha(128); + + QPen gridPen(gridColor); + gridPen.setDashPattern(QVector<qreal>() << 2 << 2); + + if (startY < endY) { + gridPen.setDashOffset(startY); + painter->setPen(gridPen); + for (int x = startX; x < endX; x += tileWidth) + painter->drawLine(x, startY, x, endY - 1); + } + + if (startX < endX) { + gridPen.setDashOffset(startX); + painter->setPen(gridPen); + for (int y = startY; y < endY; y += tileHeight) + painter->drawLine(startX, y, endX - 1, y); + } +} + +void OrthogonalRenderer::drawTileLayer(QPainter *painter, + const TileLayer *layer, + const QRectF &exposed) const +{ + QTransform savedTransform = painter->transform(); + + const int tileWidth = map()->tileWidth(); + const int tileHeight = map()->tileHeight(); + const QPointF layerPos(layer->x() * tileWidth, + layer->y() * tileHeight); + + painter->translate(layerPos); + + int startX = 0; + int startY = 0; + int endX = layer->width(); + int endY = layer->height(); + + if (!exposed.isNull()) { + QMargins drawMargins = layer->drawMargins(); + drawMargins.setTop(drawMargins.top() - tileHeight); + drawMargins.setRight(drawMargins.right() - tileWidth); + + QRectF rect = exposed.adjusted(-drawMargins.right(), + -drawMargins.bottom(), + drawMargins.left(), + drawMargins.top()); + + rect.translate(-layerPos); + + startX = qMax((int) rect.x() / tileWidth, 0); + startY = qMax((int) rect.y() / tileHeight, 0); + endX = qMin((int) std::ceil(rect.right()) / tileWidth + 1, endX); + endY = qMin((int) std::ceil(rect.bottom()) / tileHeight + 1, endY); + } + + QTransform baseTransform = painter->transform(); + + for (int y = startY; y < endY; ++y) { + for (int x = startX; x < endX; ++x) { + const Cell &cell = layer->cellAt(x, y); + if (cell.isEmpty()) + continue; + + const QPixmap &img = cell.tile->image(); + const QPoint offset = cell.tile->tileset()->tileOffset(); + + qreal m11 = 1; // Horizontal scaling factor + qreal m12 = 0; // Vertical shearing factor + qreal m21 = 0; // Horizontal shearing factor + qreal m22 = 1; // Vertical scaling factor + qreal dx = offset.x() + x * tileWidth; + qreal dy = offset.y() + (y + 1) * tileHeight - img.height(); + + if (cell.flippedAntiDiagonally) { + // Use shearing to swap the X/Y axis + m11 = 0; + m12 = 1; + m21 = 1; + m22 = 0; + + // Compensate for the swap of image dimensions + dy += img.height() - img.width(); + } + if (cell.flippedHorizontally) { + m11 = -m11; + m21 = -m21; + dx += cell.flippedAntiDiagonally ? img.height() : img.width(); + } + if (cell.flippedVertically) { + m12 = -m12; + m22 = -m22; + dy += cell.flippedAntiDiagonally ? img.width() : img.height(); + } + + const QTransform transform(m11, m12, m21, m22, dx, dy); + painter->setTransform(transform * baseTransform); + + painter->drawPixmap(0, 0, img); + } + } + + painter->setTransform(savedTransform); +} + +void OrthogonalRenderer::drawTileSelection(QPainter *painter, + const QRegion ®ion, + const QColor &color, + const QRectF &exposed) const +{ + foreach (const QRect &r, region.rects()) { + const QRectF toFill = QRectF(boundingRect(r)).intersected(exposed); + if (!toFill.isEmpty()) + painter->fillRect(toFill, color); + } +} + +void OrthogonalRenderer::drawMapObject(QPainter *painter, + const MapObject *object, + const QColor &color) const +{ + painter->save(); + + const QRectF bounds = object->bounds(); + QRectF rect(tileToPixelCoords(bounds.topLeft()), + tileToPixelCoords(bounds.bottomRight())); + + painter->translate(rect.topLeft()); + rect.moveTopLeft(QPointF(0, 0)); + + if (object->tile()) { + const QPixmap &img = object->tile()->image(); + const QPoint paintOrigin(0, -img.height()); + painter->drawPixmap(paintOrigin, img); + + QPen pen(Qt::SolidLine); + painter->setPen(pen); + painter->drawRect(QRect(paintOrigin, img.size())); + pen.setStyle(Qt::DotLine); + pen.setColor(color); + painter->setPen(pen); + painter->drawRect(QRect(paintOrigin, img.size())); + } else { + const QPen linePen(color, 2); + const QPen shadowPen(Qt::black, 2); + + QColor brushColor = color; + brushColor.setAlpha(50); + const QBrush fillBrush(brushColor); + + painter->setRenderHint(QPainter::Antialiasing); + + switch (object->shape()) { + case MapObject::Rectangle: { + if (rect.isNull()) + rect = QRectF(QPointF(-10, -10), QSizeF(20, 20)); + + const QFontMetrics fm = painter->fontMetrics(); + QString name = fm.elidedText(object->name(), Qt::ElideRight, + rect.width() + 2); + + // Draw the shadow + painter->setPen(shadowPen); + painter->drawRect(rect.translated(QPointF(1, 1))); + if (!name.isEmpty()) + painter->drawText(QPoint(1, -5 + 1), name); + + painter->setPen(linePen); + painter->setBrush(fillBrush); + painter->drawRect(rect); + if (!name.isEmpty()) + painter->drawText(QPoint(0, -5), name); + + break; + } + + case MapObject::Polyline: { + QPolygonF screenPolygon = tileToPixelCoords(object->polygon()); + + painter->setPen(shadowPen); + painter->drawPolyline(screenPolygon.translated(1, 1)); + + painter->setPen(linePen); + painter->setBrush(fillBrush); + painter->drawPolyline(screenPolygon); + break; + } + + case MapObject::Polygon: { + QPolygonF screenPolygon = tileToPixelCoords(object->polygon()); + + painter->setPen(shadowPen); + painter->drawPolygon(screenPolygon.translated(1, 1)); + + painter->setPen(linePen); + painter->setBrush(fillBrush); + painter->drawPolygon(screenPolygon); + break; + } + } + } + + painter->restore(); +} + +QPointF OrthogonalRenderer::pixelToTileCoords(qreal x, qreal y) const +{ + return QPointF(x / map()->tileWidth(), + y / map()->tileHeight()); +} + +QPointF OrthogonalRenderer::tileToPixelCoords(qreal x, qreal y) const +{ + return QPointF(x * map()->tileWidth(), + y * map()->tileHeight()); +} diff --git a/libtiled/orthogonalrenderer.h b/libtiled/orthogonalrenderer.h new file mode 100644 index 0000000..d6afb01 --- /dev/null +++ b/libtiled/orthogonalrenderer.h @@ -0,0 +1,74 @@ +/* + * orthogonalrenderer.h + * Copyright 2009-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ORTHOGONALRENDERER_H +#define ORTHOGONALRENDERER_H + +#include "maprenderer.h" + +namespace Tiled { + +/** + * The orthogonal map renderer. This is the most basic map renderer, + * dealing with maps that use rectangular tiles. + */ +class TILEDSHARED_EXPORT OrthogonalRenderer : public MapRenderer +{ +public: + OrthogonalRenderer(const Map *map) : MapRenderer(map) {} + + QSize mapSize() const; + + QRect boundingRect(const QRect &rect) const; + + QRectF boundingRect(const MapObject *object) const; + QPainterPath shape(const MapObject *object) const; + + void drawGrid(QPainter *painter, const QRectF &rect) const; + + void drawTileLayer(QPainter *painter, const TileLayer *layer, + const QRectF &exposed = QRectF()) const; + + void drawTileSelection(QPainter *painter, + const QRegion ®ion, + const QColor &color, + const QRectF &exposed) const; + + void drawMapObject(QPainter *painter, + const MapObject *object, + const QColor &color) const; + + QPointF pixelToTileCoords(qreal x, qreal y) const; + + using MapRenderer::tileToPixelCoords; + QPointF tileToPixelCoords(qreal x, qreal y) const; +}; + +} // namespace Tiled + +#endif // ORTHOGONALRENDERER_H diff --git a/libtiled/properties.cpp b/libtiled/properties.cpp new file mode 100644 index 0000000..53e483d --- /dev/null +++ b/libtiled/properties.cpp @@ -0,0 +1,42 @@ +/* + * properties.cpp + * Copyright 2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "properties.h" + +using namespace Tiled; + +void Properties::merge(const Properties &other) +{ + // Based on QMap::unite, but using insert instead of insertMulti + const_iterator it = other.constEnd(); + const const_iterator b = other.constBegin(); + while (it != b) { + --it; + insert(it.key(), it.value()); + } +} diff --git a/libtiled/properties.h b/libtiled/properties.h new file mode 100644 index 0000000..4c34087 --- /dev/null +++ b/libtiled/properties.h @@ -0,0 +1,47 @@ +/* + * properties.h + * Copyright 2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PROPERTIES_H +#define PROPERTIES_H + +#include "tiled_global.h" + +#include <QMap> +#include <QString> + +namespace Tiled { + +class TILEDSHARED_EXPORT Properties : public QMap<QString,QString> +{ +public: + void merge(const Properties &other); +}; + +} // namespace Tiled + +#endif // PROPERTIES_H diff --git a/libtiled/tile.h b/libtiled/tile.h new file mode 100644 index 0000000..b512622 --- /dev/null +++ b/libtiled/tile.h @@ -0,0 +1,93 @@ +/* + * tile.h + * Copyright 2008-2009, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * Copyright 2009, Edward Hutchins <eah1@yahoo.com> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TILE_H +#define TILE_H + +#include "object.h" + +#include <QPixmap> + +namespace Tiled { + +class Tileset; + +class TILEDSHARED_EXPORT Tile : public Object +{ +public: + Tile(const QPixmap &image, int id, Tileset *tileset): + mId(id), + mTileset(tileset), + mImage(image) + {} + + /** + * Returns ID of this tile within its tileset. + */ + int id() const { return mId; } + + /** + * Returns the tileset that this tile is part of. + */ + Tileset *tileset() const { return mTileset; } + + /** + * Returns the image of this tile. + */ + const QPixmap &image() const { return mImage; } + + /** + * Sets the image of this tile. + */ + void setImage(const QPixmap &image) { mImage = image; } + + /** + * Returns the width of this tile. + */ + int width() const { return mImage.width(); } + + /** + * Returns the height of this tile. + */ + int height() const { return mImage.height(); } + + /** + * Returns the size of this tile. + */ + QSize size() const { return mImage.size(); } + +private: + int mId; + Tileset *mTileset; + QPixmap mImage; +}; + +} // namespace Tiled + +#endif // TILE_H diff --git a/libtiled/tiled_global.h b/libtiled/tiled_global.h new file mode 100644 index 0000000..3dbf164 --- /dev/null +++ b/libtiled/tiled_global.h @@ -0,0 +1,40 @@ +/* + * tiled_global.h + * Copyright 2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TILED_GLOBAL_H +#define TILED_GLOBAL_H + +#include <QtCore/qglobal.h> + +#if defined(TILED_LIBRARY) +# define TILEDSHARED_EXPORT Q_DECL_EXPORT +#else +# define TILEDSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // TILED_GLOBAL_H diff --git a/libtiled/tilelayer.cpp b/libtiled/tilelayer.cpp new file mode 100644 index 0000000..98675a7 --- /dev/null +++ b/libtiled/tilelayer.cpp @@ -0,0 +1,425 @@ +/* + * tilelayer.cpp + * Copyright 2008-2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * Copyright 2009, Jeff Bland <jksb@member.fsf.org> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tilelayer.h" + +#include "map.h" +#include "tile.h" +#include "tileset.h" + +using namespace Tiled; + +TileLayer::TileLayer(const QString &name, int x, int y, int width, int height): + Layer(name, x, y, width, height), + mMaxTileSize(0, 0), + mGrid(width * height) +{ + Q_ASSERT(width >= 0); + Q_ASSERT(height >= 0); +} + +QRegion TileLayer::region() const +{ + QRegion region; + + for (int y = 0; y < mHeight; ++y) { + for (int x = 0; x < mWidth; ++x) { + if (!cellAt(x, y).isEmpty()) { + const int rangeStart = x; + for (++x; x <= mWidth; ++x) { + if (x == mWidth || cellAt(x, y).isEmpty()) { + const int rangeEnd = x; + region += QRect(rangeStart + mX, y + mY, + rangeEnd - rangeStart, 1); + break; + } + } + } + } + } + + return region; +} + +static QSize maxSize(const QSize &a, + const QSize &b) +{ + return QSize(qMax(a.width(), b.width()), + qMax(a.height(), b.height())); +} + +static QMargins maxMargins(const QMargins &a, + const QMargins &b) +{ + return QMargins(qMax(a.left(), b.left()), + qMax(a.top(), b.top()), + qMax(a.right(), b.right()), + qMax(a.bottom(), b.bottom())); +} + +void TileLayer::setCell(int x, int y, const Cell &cell) +{ + Q_ASSERT(contains(x, y)); + + if (cell.tile) { + int width = cell.tile->width(); + int height = cell.tile->height(); + + if (cell.flippedAntiDiagonally) + std::swap(width, height); + + const QPoint offset = cell.tile->tileset()->tileOffset(); + + mMaxTileSize = maxSize(QSize(width, height), mMaxTileSize); + mOffsetMargins = maxMargins(QMargins(-offset.x(), + -offset.y(), + offset.x(), + offset.y()), + mOffsetMargins); + + if (mMap) + mMap->adjustDrawMargins(drawMargins()); + } + + mGrid[x + y * mWidth] = cell; +} + +TileLayer *TileLayer::copy(const QRegion ®ion) const +{ + const QRegion area = region.intersected(QRect(0, 0, width(), height())); + const QRect bounds = region.boundingRect(); + const QRect areaBounds = area.boundingRect(); + const int offsetX = qMax(0, areaBounds.x() - bounds.x()); + const int offsetY = qMax(0, areaBounds.y() - bounds.y()); + + TileLayer *copied = new TileLayer(QString(), + 0, 0, + bounds.width(), bounds.height()); + + foreach (const QRect &rect, area.rects()) + for (int x = rect.left(); x <= rect.right(); ++x) + for (int y = rect.top(); y <= rect.bottom(); ++y) + copied->setCell(x - areaBounds.x() + offsetX, + y - areaBounds.y() + offsetY, + cellAt(x, y)); + + return copied; +} + +void TileLayer::merge(const QPoint &pos, const TileLayer *layer) +{ + // Determine the overlapping area + QRect area = QRect(pos, QSize(layer->width(), layer->height())); + area &= QRect(0, 0, width(), height()); + + for (int y = area.top(); y <= area.bottom(); ++y) { + for (int x = area.left(); x <= area.right(); ++x) { + const Cell &cell = layer->cellAt(x - area.left(), + y - area.top()); + if (!cell.isEmpty()) + setCell(x, y, cell); + } + } +} + +void TileLayer::setCells(int x, int y, TileLayer *layer, + const QRegion &mask) +{ + // Determine the overlapping area + QRegion area = QRect(x, y, layer->width(), layer->height()); + area &= QRect(0, 0, width(), height()); + + if (!mask.isEmpty()) + area &= mask; + + foreach (const QRect &rect, area.rects()) + for (int _x = rect.left(); _x <= rect.right(); ++_x) + for (int _y = rect.top(); _y <= rect.bottom(); ++_y) + setCell(_x, _y, layer->cellAt(_x - x, _y - y)); +} + +void TileLayer::flip(FlipDirection direction) +{ + QVector<Cell> newGrid(mWidth * mHeight); + + Q_ASSERT(direction == FlipHorizontally || direction == FlipVertically); + + for (int y = 0; y < mHeight; ++y) { + for (int x = 0; x < mWidth; ++x) { + Cell &dest = newGrid[x + y * mWidth]; + if (direction == FlipHorizontally) { + const Cell &source = cellAt(mWidth - x - 1, y); + dest = source; + dest.flippedHorizontally = !source.flippedHorizontally; + } else if (direction == FlipVertically) { + const Cell &source = cellAt(x, mHeight - y - 1); + dest = source; + dest.flippedVertically = !source.flippedVertically; + } + } + } + + mGrid = newGrid; +} + +void TileLayer::rotate(RotateDirection direction) +{ + static const char rotateRightMask[8] = { 5, 4, 1, 0, 7, 6, 3, 2 }; + static const char rotateLeftMask[8] = { 3, 2, 7, 6, 1, 0, 5, 4 }; + + const char (&rotateMask)[8] = + (direction == RotateRight) ? rotateRightMask : rotateLeftMask; + + int newWidth = mHeight; + int newHeight = mWidth; + QVector<Cell> newGrid(newWidth * newHeight); + + for (int y = 0; y < mHeight; ++y) { + for (int x = 0; x < mWidth; ++x) { + const Cell &source = cellAt(x, y); + Cell dest = source; + + unsigned char mask = + (dest.flippedHorizontally << 2) | + (dest.flippedVertically << 1) | + (dest.flippedAntiDiagonally << 0); + + mask = rotateMask[mask]; + + dest.flippedHorizontally = (mask & 4) != 0; + dest.flippedVertically = (mask & 2) != 0; + dest.flippedAntiDiagonally = (mask & 1) != 0; + + if (direction == RotateRight) + newGrid[x * newWidth + (mHeight - y - 1)] = dest; + else + newGrid[(mWidth - x - 1) * newWidth + y] = dest; + } + } + + std::swap(mMaxTileSize.rwidth(), + mMaxTileSize.rheight()); + + mWidth = newWidth; + mHeight = newHeight; + mGrid = newGrid; +} + + +QSet<Tileset*> TileLayer::usedTilesets() const +{ + QSet<Tileset*> tilesets; + + for (int i = 0, i_end = mGrid.size(); i < i_end; ++i) + if (const Tile *tile = mGrid.at(i).tile) + tilesets.insert(tile->tileset()); + + return tilesets; +} + +bool TileLayer::referencesTileset(const Tileset *tileset) const +{ + for (int i = 0, i_end = mGrid.size(); i < i_end; ++i) { + const Tile *tile = mGrid.at(i).tile; + if (tile && tile->tileset() == tileset) + return true; + } + return false; +} + +QRegion TileLayer::tilesetReferences(Tileset *tileset) const +{ + QRegion region; + + for (int y = 0; y < mHeight; ++y) + for (int x = 0; x < mWidth; ++x) + if (const Tile *tile = cellAt(x, y).tile) + if (tile->tileset() == tileset) + region += QRegion(x + mX, y + mY, 1, 1); + + return region; +} + +void TileLayer::removeReferencesToTileset(Tileset *tileset) +{ + for (int i = 0, i_end = mGrid.size(); i < i_end; ++i) { + const Tile *tile = mGrid.at(i).tile; + if (tile && tile->tileset() == tileset) + mGrid.replace(i, Cell()); + } +} + +void TileLayer::replaceReferencesToTileset(Tileset *oldTileset, + Tileset *newTileset) +{ + for (int i = 0, i_end = mGrid.size(); i < i_end; ++i) { + const Tile *tile = mGrid.at(i).tile; + if (tile && tile->tileset() == oldTileset) + mGrid[i].tile = newTileset->tileAt(tile->id()); + } +} + +void TileLayer::resize(const QSize &size, const QPoint &offset) +{ + QVector<Cell> newGrid(size.width() * size.height()); + + // Copy over the preserved part + const int startX = qMax(0, -offset.x()); + const int startY = qMax(0, -offset.y()); + const int endX = qMin(mWidth, size.width() - offset.x()); + const int endY = qMin(mHeight, size.height() - offset.y()); + + for (int y = startY; y < endY; ++y) { + for (int x = startX; x < endX; ++x) { + const int index = x + offset.x() + (y + offset.y()) * size.width(); + newGrid[index] = cellAt(x, y); + } + } + + mGrid = newGrid; + Layer::resize(size, offset); +} + +void TileLayer::offset(const QPoint &offset, + const QRect &bounds, + bool wrapX, bool wrapY) +{ + QVector<Cell> newGrid(mWidth * mHeight); + + for (int y = 0; y < mHeight; ++y) { + for (int x = 0; x < mWidth; ++x) { + // Skip out of bounds tiles + if (!bounds.contains(x, y)) { + newGrid[x + y * mWidth] = cellAt(x, y); + continue; + } + + // Get position to pull tile value from + int oldX = x - offset.x(); + int oldY = y - offset.y(); + + // Wrap x value that will be pulled from + if (wrapX && bounds.width() > 0) { + while (oldX < bounds.left()) + oldX += bounds.width(); + while (oldX > bounds.right()) + oldX -= bounds.width(); + } + + // Wrap y value that will be pulled from + if (wrapY && bounds.height() > 0) { + while (oldY < bounds.top()) + oldY += bounds.height(); + while (oldY > bounds.bottom()) + oldY -= bounds.height(); + } + + // Set the new tile + if (contains(oldX, oldY) && bounds.contains(oldX, oldY)) + newGrid[x + y * mWidth] = cellAt(oldX, oldY); + else + newGrid[x + y * mWidth] = Cell(); + } + } + + mGrid = newGrid; +} + +bool TileLayer::canMergeWith(Layer *other) const +{ + return dynamic_cast<TileLayer*>(other) != 0; +} + +Layer *TileLayer::mergedWith(Layer *other) const +{ + Q_ASSERT(canMergeWith(other)); + + const TileLayer *o = static_cast<TileLayer*>(other); + const QRect unitedBounds = bounds().united(o->bounds()); + const QPoint offset = position() - unitedBounds.topLeft(); + + TileLayer *merged = static_cast<TileLayer*>(clone()); + merged->resize(unitedBounds.size(), offset); + merged->merge(o->position() - unitedBounds.topLeft(), o); + return merged; +} + +QRegion TileLayer::computeDiffRegion(const TileLayer *other) const +{ + QRegion ret; + + const int dx = other->x() - mX; + const int dy = other->y() - mY; + QRect r = QRect(0, 0, width(), height()); + r &= QRect(dx, dy, other->width(), other->height()); + + for (int y = r.top(); y <= r.bottom(); ++y) { + for (int x = r.left(); x <= r.right(); ++x) { + if (cellAt(x, y) != other->cellAt(x - dx, y - dy)) { + const int rangeStart = x; + while (x <= r.right() && + cellAt(x, y) != other->cellAt(x - dx, y - dy)) { + ++x; + } + const int rangeEnd = x; + ret += QRect(rangeStart, y, rangeEnd - rangeStart, 1); + } + } + } + + return ret; +} + +bool TileLayer::isEmpty() const +{ + for (int i = 0, i_end = mGrid.size(); i < i_end; ++i) + if (!mGrid.at(i).isEmpty()) + return false; + + return true; +} + +/** + * Returns a duplicate of this TileLayer. + * + * \sa Layer::clone() + */ +Layer *TileLayer::clone() const +{ + return initializeClone(new TileLayer(mName, mX, mY, mWidth, mHeight)); +} + +TileLayer *TileLayer::initializeClone(TileLayer *clone) const +{ + Layer::initializeClone(clone); + clone->mGrid = mGrid; + clone->mMaxTileSize = mMaxTileSize; + clone->mOffsetMargins = mOffsetMargins; + return clone; +} diff --git a/libtiled/tilelayer.h b/libtiled/tilelayer.h new file mode 100644 index 0000000..eca8e88 --- /dev/null +++ b/libtiled/tilelayer.h @@ -0,0 +1,279 @@ +/* + * tilelayer.h + * Copyright 2008-2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * Copyright 2009, Jeff Bland <jksb@member.fsf.org> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TILELAYER_H +#define TILELAYER_H + +#include "tiled_global.h" + +#include "layer.h" + +#include <QMargins> +#include <QString> +#include <QVector> + +namespace Tiled { + +class Tile; +class Tileset; + +/** + * A cell on a tile layer grid. + */ +class Cell +{ +public: + Cell() : + tile(0), + flippedHorizontally(false), + flippedVertically(false), + flippedAntiDiagonally(false) + {} + + explicit Cell(Tile *tile) : + tile(tile), + flippedHorizontally(false), + flippedVertically(false), + flippedAntiDiagonally(false) + {} + + bool isEmpty() const { return tile == 0; } + + bool operator == (const Cell &other) const + { + return tile == other.tile + && flippedHorizontally == other.flippedHorizontally + && flippedVertically == other.flippedVertically + && flippedAntiDiagonally == other.flippedAntiDiagonally; + } + + bool operator != (const Cell &other) const + { + return tile != other.tile + || flippedHorizontally != other.flippedHorizontally + || flippedVertically != other.flippedVertically + || flippedAntiDiagonally != other.flippedAntiDiagonally; + } + + Tile *tile; + bool flippedHorizontally; + bool flippedVertically; + bool flippedAntiDiagonally; +}; + +/** + * A tile layer is a grid of cells. Each cell refers to a specific tile, and + * stores how the tile is flipped. + * + * Coordinates and regions passed to function parameters are in local + * coordinates and do not take into account the position of the layer. + */ +class TILEDSHARED_EXPORT TileLayer : public Layer +{ +public: + enum FlipDirection { + FlipHorizontally, + FlipVertically, + FlipDiagonally + }; + + enum RotateDirection { + RotateLeft, + RotateRight + }; + + /** + * Constructor. + */ + TileLayer(const QString &name, int x, int y, int width, int height); + + /** + * Returns the maximum tile size of this layer. + */ + QSize maxTileSize() const { return mMaxTileSize; } + + /** + * Returns the margins that have to be taken into account while drawing + * this tile layer. The margins depend on the maximum tile size and the + * offset applied to the tiles. + */ + QMargins drawMargins() const + { + return QMargins(mOffsetMargins.left(), + mOffsetMargins.top() + mMaxTileSize.height(), + mOffsetMargins.right() + mMaxTileSize.width(), + mOffsetMargins.bottom()); + } + + + /** + * Returns whether (x, y) is inside this map layer. + */ + bool contains(int x, int y) const + { return x >= 0 && y >= 0 && x < mWidth && y < mHeight; } + + bool contains(const QPoint &point) const + { return contains(point.x(), point.y()); } + + /** + * Calculates the region occupied by the tiles of this layer. Similar to + * Layer::bounds(), but leaves out the regions without tiles. + */ + QRegion region() const; + + /** + * Returns a read-only reference to the cell at the given coordinates. The + * coordinates have to be within this layer. + */ + const Cell &cellAt(int x, int y) const + { return mGrid.at(x + y * mWidth); } + + const Cell &cellAt(const QPoint &point) const + { return cellAt(point.x(), point.y()); } + + /** + * Sets the cell at the given coordinates. + */ + void setCell(int x, int y, const Cell &cell); + + /** + * Returns a copy of the area specified by the given \a region. The + * caller is responsible for the returned tile layer. + */ + TileLayer *copy(const QRegion ®ion) const; + + TileLayer *copy(int x, int y, int width, int height) const + { return copy(QRegion(x, y, width, height)); } + + /** + * Merges the given \a layer onto this layer at position \a pos. Parts that + * fall outside of this layer will be lost and empty tiles in the given + * layer will have no effect. + */ + void merge(const QPoint &pos, const TileLayer *layer); + + /** + * Sets the cells starting at the given position to the cells in the given + * \a tileLayer. Parts that fall outside of this layer will be ignored. + * + * When a \a mask is given, only cells that fall within this mask are set. + * The mask is applied in local coordinates. + */ + void setCells(int x, int y, TileLayer *tileLayer, + const QRegion &mask = QRegion()); + + /** + * Flip this tile layer in the given \a direction. Direction must be + * horizontal or vertical. This doesn't change the dimensions of the + * tile layer. + */ + void flip(FlipDirection direction); + + /** + * Rotate this tile layer by 90 degrees left or right. The tile positions + * are rotated within the layer, and the tiles themselves are rotated. The + * dimensions of the tile layer are swapped. + */ + void rotate(RotateDirection direction); + + /** + * Computes and returns the set of tilesets used by this tile layer. + */ + QSet<Tileset*> usedTilesets() const; + + /** + * Returns whether this tile layer is referencing the given tileset. + */ + bool referencesTileset(const Tileset *tileset) const; + + /** + * Returns the region of tiles coming from the given \a tileset. + */ + QRegion tilesetReferences(Tileset *tileset) const; + + /** + * Removes all references to the given tileset. This sets all tiles on this + * layer that are from the given tileset to null. + */ + void removeReferencesToTileset(Tileset *tileset); + + /** + * Replaces all tiles from \a oldTileset with tiles from \a newTileset. + */ + void replaceReferencesToTileset(Tileset *oldTileset, Tileset *newTileset); + + /** + * Resizes this tile layer to \a size, while shifting all tiles by + * \a offset. + * + * \sa Layer::resize() + */ + virtual void resize(const QSize &size, const QPoint &offset); + + /** + * Offsets the objects in this group by \a offset, within \bounds + * and optionally wraps it. + * + * \sa Layer::offset() + */ + virtual void offset(const QPoint &offset, + const QRect &bounds, + bool wrapX, bool wrapY); + + bool canMergeWith(Layer *other) const; + Layer *mergedWith(Layer *other) const; + + /** + * Returns the region where this tile layer and the given tile layer + * are different. The relative positions of the layers are taken into + * account. The returned region is relative to this tile layer. + */ + QRegion computeDiffRegion(const TileLayer *other) const; + + /** + * Returns true if all tiles in the layer are empty. + */ + bool isEmpty() const; + + virtual Layer *clone() const; + + virtual TileLayer *asTileLayer() { return this; } + +protected: + TileLayer *initializeClone(TileLayer *clone) const; + +private: + QSize mMaxTileSize; + QMargins mOffsetMargins; + QVector<Cell> mGrid; +}; + +} // namespace Tiled + +#endif // TILELAYER_H diff --git a/libtiled/tileset.cpp b/libtiled/tileset.cpp new file mode 100644 index 0000000..54e71e9 --- /dev/null +++ b/libtiled/tileset.cpp @@ -0,0 +1,114 @@ +/* + * tileset.cpp + * Copyright 2008-2009, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * Copyrigth 2009, Edward Hutchins <eah1@yahoo.com> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tileset.h" +#include "tile.h" + +#include <QBitmap> + +using namespace Tiled; + +Tileset::~Tileset() +{ + qDeleteAll(mTiles); +} + +Tile *Tileset::tileAt(int id) const +{ + return (id < mTiles.size()) ? mTiles.at(id) : 0; +} + +bool Tileset::loadFromImage(const QImage &image, const QString &fileName) +{ + Q_ASSERT(mTileWidth > 0 && mTileHeight > 0); + + if (image.isNull()) + return false; + + const int stopWidth = image.width() - mTileWidth; + const int stopHeight = image.height() - mTileHeight; + + int oldTilesetSize = mTiles.size(); + int tileNum = 0; + + for (int y = mMargin; y <= stopHeight; y += mTileHeight + mTileSpacing) { + for (int x = mMargin; x <= stopWidth; x += mTileWidth + mTileSpacing) { + const QImage tileImage = image.copy(x, y, mTileWidth, mTileHeight); + QPixmap tilePixmap = QPixmap::fromImage(tileImage); + + if (mTransparentColor.isValid()) { + const QImage mask = + tileImage.createMaskFromColor(mTransparentColor.rgb()); + tilePixmap.setMask(QBitmap::fromImage(mask)); + } + + if (tileNum < oldTilesetSize) { + mTiles.at(tileNum)->setImage(tilePixmap); + } else { + mTiles.append(new Tile(tilePixmap, tileNum, this)); + } + ++tileNum; + } + } + + // Blank out any remaining tiles to avoid confusion + while (tileNum < oldTilesetSize) { + QPixmap tilePixmap = QPixmap(mTileWidth, mTileHeight); + tilePixmap.fill(); + mTiles.at(tileNum)->setImage(tilePixmap); + ++tileNum; + } + + mImageWidth = image.width(); + mImageHeight = image.height(); + mColumnCount = columnCountForWidth(mImageWidth); + mImageSource = fileName; + return true; +} + +Tileset *Tileset::findSimilarTileset(const QList<Tileset*> &tilesets) const +{ + foreach (Tileset *candidate, tilesets) { + if (candidate != this + && candidate->imageSource() == imageSource() + && candidate->tileWidth() == tileWidth() + && candidate->tileHeight() == tileHeight() + && candidate->tileSpacing() == tileSpacing() + && candidate->margin() == margin()) { + return candidate; + } + } + return 0; +} + +int Tileset::columnCountForWidth(int width) const +{ + Q_ASSERT(mTileWidth > 0); + return (width - mMargin + mTileSpacing) / (mTileWidth + mTileSpacing); +} diff --git a/libtiled/tileset.h b/libtiled/tileset.h new file mode 100644 index 0000000..99c7ba6 --- /dev/null +++ b/libtiled/tileset.h @@ -0,0 +1,234 @@ +/* + * tileset.h + * Copyright 2008-2009, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> + * Copyrigth 2009, Edward Hutchins <eah1@yahoo.com> + * + * This file is part of libtiled. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TILESET_H +#define TILESET_H + +#include "object.h" + +#include <QColor> +#include <QList> +#include <QPoint> +#include <QString> + +class QImage; + +namespace Tiled { + +class Tile; + +/** + * A tileset, representing a set of tiles. + * + * This class currently only supports loading tiles from a tileset image, using + * loadFromImage(). There is no way to add or remove arbitrary tiles. + */ +class TILEDSHARED_EXPORT Tileset : public Object +{ +public: + /** + * Constructor. + * + * @param name the name of the tileset + * @param tileWidth the width of the tiles in the tileset + * @param tileHeight the height of the tiles in the tileset + * @param tileSpacing the spacing between the tiles in the tileset image + * @param margin the margin around the tiles in the tileset image + */ + Tileset(const QString &name, int tileWidth, int tileHeight, + int tileSpacing = 0, int margin = 0): + mName(name), + mTileWidth(tileWidth), + mTileHeight(tileHeight), + mTileSpacing(tileSpacing), + mMargin(margin), + mImageWidth(0), + mImageHeight(0), + mColumnCount(0) + { + Q_ASSERT(tileSpacing >= 0); + Q_ASSERT(margin >= 0); + } + + /** + * Destructor. + */ + ~Tileset(); + + /** + * Returns the name of this tileset. + */ + const QString &name() const { return mName; } + + /** + * Sets the name of this tileset. + */ + void setName(const QString &name) { mName = name; } + + /** + * Returns the file name of this tileset. When the tileset isn't an + * external tileset, the file name is empty. + */ + const QString &fileName() const { return mFileName; } + + /** + * Sets the filename of this tileset. + */ + void setFileName(const QString &fileName) { mFileName = fileName; } + + /** + * Returns whether this tileset is external. + */ + bool isExternal() const { return !mFileName.isEmpty(); } + + /** + * Returns the width of the tiles in this tileset. + */ + int tileWidth() const { return mTileWidth; } + + /** + * Returns the height of the tiles in this tileset. + */ + int tileHeight() const { return mTileHeight; } + + /** + * Returns the spacing between the tiles in the tileset image. + */ + int tileSpacing() const { return mTileSpacing; } + + /** + * Returns the margin around the tiles in the tileset image. + */ + int margin() const { return mMargin; } + + /** + * Returns the offset that is applied when drawing the tiles in this + * tileset. + */ + QPoint tileOffset() const { return mTileOffset; } + + /** + * @see tileOffset + */ + void setTileOffset(QPoint offset) { mTileOffset = offset; } + + /** + * Returns the tile for the given tile ID. + * The tile ID is local to this tileset, which means the IDs are in range + * [0, tileCount() - 1]. + */ + Tile *tileAt(int id) const; + + /** + * Returns the number of tiles in this tileset. + */ + int tileCount() const { return mTiles.size(); } + + /** + * Returns the number of tile columns in the tileset image. + */ + int columnCount() const { return mColumnCount; } + + /** + * Returns the width of the tileset image. + */ + int imageWidth() const { return mImageWidth; } + + /** + * Returns the height of the tileset image. + */ + int imageHeight() const { return mImageHeight; } + + /** + * Returns the transparent color, or an invalid color if no transparent + * color is used. + */ + QColor transparentColor() const { return mTransparentColor; } + + /** + * Sets the transparent color. Pixels with this color will be masked out + * when loadFromImage() is called. + */ + void setTransparentColor(const QColor &c) { mTransparentColor = c; } + + /** + * Load this tileset from the given tileset \a image. This will replace + * existing tile images in this tileset with new ones. If the new image + * contains more tiles than exist in the tileset new tiles will be + * appended, if there are fewer tiles the excess images will be blanked. + * + * The tile width and height of this tileset must be higher than 0. + * + * @param image the image to load the tiles from + * @param fileName the file name of the image, which will be remembered + * as the image source of this tileset. + * @return <code>true</code> if loading was successful, otherwise + * returns <code>false</code> + */ + bool loadFromImage(const QImage &image, const QString &fileName); + + /** + * This checks if there is a similar tileset in the given list. + * It is needed for replacing this tileset by its similar copy. + */ + Tileset *findSimilarTileset(const QList<Tileset*> &tilesets) const; + + /** + * Returns the file name of the external image that contains the tiles in + * this tileset. Is an empty string when this tileset doesn't have a + * tileset image. + */ + const QString &imageSource() const { return mImageSource; } + + /** + * Returns the column count that this tileset would have if the tileset + * image would have the given \a width. This takes into account the tile + * size, margin and spacing. + */ + int columnCountForWidth(int width) const; + +private: + QString mName; + QString mFileName; + QString mImageSource; + QColor mTransparentColor; + int mTileWidth; + int mTileHeight; + int mTileSpacing; + int mMargin; + QPoint mTileOffset; + int mImageWidth; + int mImageHeight; + int mColumnCount; + QList<Tile*> mTiles; +}; + +} // namespace Tiled + +#endif // TILESET_H |
