Skip to content

Commit

Permalink
Use Tile::imageRect instead of cutting out tiles
Browse files Browse the repository at this point in the history
Now that we're storing a sub-rectangle for each tile, we can use it to
avoid actually cutting out the tiles from the tileset image.

Changing of a tile's image rect now also has its own undo command, and
the tile image rect property is visible also for tiles from
tileset-image based tilesets, though not editable for now.
  • Loading branch information
bjorn committed May 23, 2022
1 parent 3ac1ca2 commit 7f7589e
Show file tree
Hide file tree
Showing 20 changed files with 198 additions and 286 deletions.
88 changes: 0 additions & 88 deletions src/libtiled/imagecache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,39 +39,6 @@

namespace Tiled {

bool TilesheetParameters::operator==(const TilesheetParameters &other) const
{
return fileName == other.fileName &&
tileWidth == other.tileWidth &&
tileHeight == other.tileHeight &&
margin == other.margin &&
spacing == other.spacing &&
transparentColor == other.transparentColor;
}

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
uint qHash(const TilesheetParameters &key, uint seed) Q_DECL_NOTHROW
#else
size_t qHash(const TilesheetParameters &key, size_t seed) Q_DECL_NOTHROW
#endif
{
auto h = ::qHash(key.fileName, seed);
h = ::qHash(key.tileWidth, h);
h = ::qHash(key.tileHeight, h);
h = ::qHash(key.margin, h);
h = ::qHash(key.spacing, h);
h = ::qHash(key.transparentColor.rgba(), h);
return h;
}

struct CutTiles
{
operator const QVector<QPixmap> &() const { return tiles; }

QVector<QPixmap> tiles;
QDateTime lastModified;
};

struct LoadedPixmap
{
explicit LoadedPixmap(const LoadedImage &cachedImage);
Expand Down Expand Up @@ -101,7 +68,6 @@ LoadedPixmap::LoadedPixmap(const LoadedImage &cachedImage)

QHash<QString, LoadedImage> ImageCache::sLoadedImages;
QHash<QString, LoadedPixmap> ImageCache::sLoadedPixmaps;
QHash<TilesheetParameters, CutTiles> ImageCache::sCutTiles;

LoadedImage ImageCache::loadImage(const QString &fileName)
{
Expand Down Expand Up @@ -148,64 +114,10 @@ QPixmap ImageCache::loadPixmap(const QString &fileName)
return it.value();
}

static CutTiles cutTilesImpl(const TilesheetParameters &p)
{
Q_ASSERT(p.tileWidth > 0 && p.tileHeight > 0);

const LoadedImage loadedImage = ImageCache::loadImage(p.fileName);
const QImage &image = loadedImage.image;
const int stopWidth = image.width() - p.tileWidth;
const int stopHeight = image.height() - p.tileHeight;

CutTiles result;
result.lastModified = loadedImage.lastModified;

for (int y = p.margin; y <= stopHeight; y += p.tileHeight + p.spacing) {
for (int x = p.margin; x <= stopWidth; x += p.tileWidth + p.spacing) {
const QImage tileImage = image.copy(x, y, p.tileWidth, p.tileHeight);
QPixmap tilePixmap = QPixmap::fromImage(tileImage);

if (p.transparentColor.isValid()) {
const QImage mask = tileImage.createMaskFromColor(p.transparentColor.rgb());
tilePixmap.setMask(QBitmap::fromImage(mask));
}

result.tiles.append(tilePixmap);
}
}

return result;
}

QVector<QPixmap> ImageCache::cutTiles(const TilesheetParameters &parameters)
{
if (parameters.fileName.isEmpty())
return {};

auto it = sCutTiles.find(parameters);

bool found = it != sCutTiles.end();
bool old = found && it.value().lastModified < QFileInfo(parameters.fileName).lastModified();

if (old)
remove(parameters.fileName);
if (old || !found)
it = sCutTiles.insert(parameters, cutTilesImpl(parameters));

return it.value();
}

void ImageCache::remove(const QString &fileName)
{
sLoadedImages.remove(fileName);
sLoadedPixmaps.remove(fileName);

// Also remove any previously cut tiles
QMutableHashIterator<TilesheetParameters, CutTiles> it(sCutTiles);
while (it.hasNext()) {
if (it.next().key().fileName == fileName)
it.remove();
}
}

QImage ImageCache::renderMap(const QString &fileName)
Expand Down
21 changes: 0 additions & 21 deletions src/libtiled/imagecache.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,6 @@

namespace Tiled {

struct TILEDSHARED_EXPORT TilesheetParameters
{
QString fileName;
int tileWidth;
int tileHeight;
int spacing;
int margin;
QColor transparentColor;

bool operator==(const TilesheetParameters &other) const;
};

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
uint TILEDSHARED_EXPORT qHash(const TilesheetParameters &key, uint seed = 0) Q_DECL_NOTHROW;
#else
size_t TILEDSHARED_EXPORT qHash(const TilesheetParameters &key, size_t seed = 0) Q_DECL_NOTHROW;
#endif

struct LoadedImage
{
LoadedImage();
Expand All @@ -68,7 +50,6 @@ struct LoadedImage
QDateTime lastModified;
};

struct CutTiles;
struct LoadedPixmap;
class Map;

Expand All @@ -77,7 +58,6 @@ class TILEDSHARED_EXPORT ImageCache
public:
static LoadedImage loadImage(const QString &fileName);
static QPixmap loadPixmap(const QString &fileName);
static QVector<QPixmap> cutTiles(const TilesheetParameters &parameters);

static void remove(const QString &fileName);

Expand All @@ -86,7 +66,6 @@ class TILEDSHARED_EXPORT ImageCache

static QHash<QString, LoadedImage> sLoadedImages;
static QHash<QString, LoadedPixmap> sLoadedPixmaps;
static QHash<TilesheetParameters, CutTiles> sCutTiles;
};

} // namespace Tiled
12 changes: 7 additions & 5 deletions src/libtiled/mapreader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,12 @@ void MapReaderPrivate::readTilesetTile(Tileset &tileset)

Tile *tile = tileset.findOrCreateTile(id);

const QRect imageRect(atts.value(QLatin1String("x")).toInt(),
atts.value(QLatin1String("y")).toInt(),
atts.value(QLatin1String("width")).toInt(),
atts.value(QLatin1String("height")).toInt());
tile->setImageRect(imageRect);

tile->setType(atts.value(QLatin1String("type")).toString());

// Read tile quadrant terrain ids as Wang IDs. This is possible because the
Expand Down Expand Up @@ -539,11 +545,7 @@ void MapReaderPrivate::readTilesetTile(Tileset &tileset)
if (imageReference.source.isEmpty())
xml.raiseError(tr("Error reading embedded image for tile %1").arg(id));
}
const QRect imageRect(atts.value(QLatin1String("x")).toInt(),
atts.value(QLatin1String("y")).toInt(),
atts.value(QLatin1String("width")).toInt(),
atts.value(QLatin1String("height")).toInt());
tileset.setTileImage(tile, image, imageReference.source, imageRect);
tileset.setTileImage(tile, image, imageReference.source);
}
} else if (xml.name() == QLatin1String("objectgroup")) {
std::unique_ptr<ObjectGroup> objectGroup = readObjectGroup();
Expand Down
2 changes: 1 addition & 1 deletion src/libtiled/maptovariantconverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ QVariant MapToVariantConverter::toVariant(const Tileset &tileset,
}

const QRect &imageRect = tile->imageRect();
if (!imageRect.isNull() && imageRect != tile->image().rect()) {
if (!imageRect.isNull() && imageRect != tile->image().rect() && tileset.isCollection()) {
tileVariant[QStringLiteral("x")] = imageRect.x();
tileVariant[QStringLiteral("y")] = imageRect.y();
tileVariant[QStringLiteral("width")] = imageRect.width();
Expand Down
30 changes: 16 additions & 14 deletions src/libtiled/mapwriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -410,31 +410,33 @@ void MapWriterPrivate::writeTileset(QXmlStreamWriter &w, const Tileset &tileset,
w.writeEndElement();
}

const bool includeAllTiles = imageSource.isEmpty() || tileset.anyTileOutOfOrder();
const bool isCollection = tileset.isCollection();
const bool includeAllTiles = isCollection || tileset.anyTileOutOfOrder();

for (const Tile *tile : tileset.tiles()) {
if (includeAllTiles || includeTile(tile)) {
w.writeStartElement(QStringLiteral("tile"));
w.writeAttribute(QStringLiteral("id"), QString::number(tile->id()));

const QRect &imageRect = tile->imageRect();
if (!imageRect.isNull() && imageRect != tile->image().rect() && isCollection) {
w.writeAttribute(QStringLiteral("x"),
QString::number(imageRect.x()));
w.writeAttribute(QStringLiteral("y"),
QString::number(imageRect.y()));
w.writeAttribute(QStringLiteral("width"),
QString::number(imageRect.width()));
w.writeAttribute(QStringLiteral("height"),
QString::number(imageRect.height()));
}

if (!tile->type().isEmpty())
w.writeAttribute(QStringLiteral("type"), tile->type());
if (tile->probability() != 1.0)
w.writeAttribute(QStringLiteral("probability"), QString::number(tile->probability()));
if (!tile->properties().isEmpty())
writeProperties(w, tile->properties());
if (imageSource.isEmpty()) {
const QRect &imageRect = tile->imageRect();
if (!imageRect.isNull() && imageRect != tile->image().rect()) {
w.writeAttribute(QStringLiteral("x"),
QString::number(imageRect.x()));
w.writeAttribute(QStringLiteral("y"),
QString::number(imageRect.y()));
w.writeAttribute(QStringLiteral("width"),
QString::number(imageRect.width()));
w.writeAttribute(QStringLiteral("height"),
QString::number(imageRect.height()));
}

if (isCollection) {
w.writeStartElement(QStringLiteral("image"));

const QSize imageSize = tile->image().isNull() ? tile->size()
Expand Down
9 changes: 9 additions & 0 deletions src/libtiled/tile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ QSharedPointer<Tileset> Tile::sharedTileset() const
return mTileset->sharedFromThis();
}

/**
* Returns the image of this tile, or the image of its tileset if it doesn't
* have an individual one.
*/
const QPixmap &Tile::image() const
{
return mImage.isNull() ? mTileset->image() : mImage;
}

// Using some internal Qt API here, but this is the function that is also used
// by QGraphicsPixmapItem, so my assumption is that it is better suited for
// this task than using QPainterPath::addRegion.
Expand Down
14 changes: 3 additions & 11 deletions src/libtiled/tile.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,6 @@ inline Tileset *Tile::tileset() const
return mTileset;
}

/**
* Returns the image of this tile.
*/
inline const QPixmap &Tile::image() const
{
return mImage;
}

/**
* Returns the URL of the external image that represents this tile.
* When this tile doesn't refer to an external image, an empty URL is
Expand Down Expand Up @@ -184,23 +176,23 @@ inline const QRect &Tile::imageRect() const
*/
inline int Tile::width() const
{
return mImageRect.isNull() ? mImage.width() : mImageRect.width();
return mImageRect.width();
}

/**
* Returns the height of this tile.
*/
inline int Tile::height() const
{
return mImageRect.isNull() ? mImage.height() : mImageRect.height();
return mImageRect.height();
}

/**
* Returns the size of this tile.
*/
inline QSize Tile::size() const
{
return mImageRect.isNull() ? mImage.size() : mImageRect.size();
return mImageRect.size();
}

/**
Expand Down
Loading

0 comments on commit 7f7589e

Please sign in to comment.