From 264e1ac28ef5a1658035a7d5dc9aad1f4df437cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Fri, 3 Nov 2023 18:27:52 +0100 Subject: [PATCH] TMX format: Support embedded images on the tileset Previously, embedded images were only supported per-tile. Due to the recent change to use sub-rects rather than physically cutting up the tileset, when you called Tileset.loadFromImage(image), the entire tileset image would get duplicated for each tile. Now the image is embedded once at the tileset level instead. Also, the Tileset.margin and Tileset.tileSpacing properties are now writable in the scripting API. Addresses part of #3630. --- NEWS.md | 1 + docs/scripting-doc/index.d.ts | 12 +++++-- src/libtiled/imagereference.cpp | 9 +++-- src/libtiled/mapreader.cpp | 7 ++-- src/libtiled/mapwriter.cpp | 29 ++++++++++++---- src/libtiled/tileset.cpp | 27 ++++++++------- src/libtiled/tileset.h | 8 ++--- src/libtiled/tilesetmanager.cpp | 2 +- src/libtiled/varianttomapconverter.cpp | 7 ++-- src/tiled/editabletileset.cpp | 46 +++++++++++++++++++++----- src/tiled/editabletileset.h | 6 ++-- 11 files changed, 104 insertions(+), 50 deletions(-) diff --git a/NEWS.md b/NEWS.md index 7e16f8d502..18255fc69f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,7 @@ * JSON format: Fixed tile order when loading a tileset using the old format * tmxrasterizer: Added --hide-object and --show-object arguments (by Lars Luz, #3819) +* Scripting: Made Tileset.margin and Tileset.tileSpacing writable * Windows: Fixed the support for WebP images (updated to Qt 6.5.3) ### Tiled 1.10.2 (4 August 2023) diff --git a/docs/scripting-doc/index.d.ts b/docs/scripting-doc/index.d.ts index 002c8e369d..194439b0c6 100644 --- a/docs/scripting-doc/index.d.ts +++ b/docs/scripting-doc/index.d.ts @@ -3336,13 +3336,21 @@ declare class Tileset extends Asset { /** * Spacing between tiles in this tileset in pixels. + * + * @note Changing this property will cause an image-based tileset to update + * all its tiles. When setting up a tileset, you'll want to set this property + * before setting the {@link image} property. */ - readonly tileSpacing : number + tileSpacing : number /** * Margin around the tileset in pixels (only used at the top and left sides of the tileset image). + * + * @note Changing this property will cause an image-based tileset to update + * all its tiles. When setting up a tileset, you'll want to set this property + * before setting the {@link image} property. */ - readonly margin : number + margin : number /** * The alignment to use for tile objects (when Unspecified, uses Bottom alignment on isometric maps and BottomLeft alignment for all other maps). diff --git a/src/libtiled/imagereference.cpp b/src/libtiled/imagereference.cpp index 630de97dca..164c772f42 100644 --- a/src/libtiled/imagereference.cpp +++ b/src/libtiled/imagereference.cpp @@ -21,7 +21,6 @@ #include "imagereference.h" #include "imagecache.h" -#include "tileset.h" namespace Tiled { @@ -33,10 +32,10 @@ bool ImageReference::hasImage() const QPixmap ImageReference::create() const { QPixmap pixmap; - if (source.isLocalFile()) - pixmap = ImageCache::loadPixmap(source.toLocalFile()); - else if (source.scheme() == QLatin1String("qrc")) - pixmap = ImageCache::loadPixmap(QLatin1Char(':') + source.path()); + + const QString fileName = Tiled::urlToLocalFileOrQrc(source); + if (!fileName.isEmpty()) + pixmap = ImageCache::loadPixmap(fileName); else if (!data.isEmpty()) pixmap = QPixmap::fromImage(QImage::fromData(data, format)); diff --git a/src/libtiled/mapreader.cpp b/src/libtiled/mapreader.cpp index 6dbb9c0ce1..820eed8b43 100644 --- a/src/libtiled/mapreader.cpp +++ b/src/libtiled/mapreader.cpp @@ -307,9 +307,8 @@ std::unique_ptr MapReaderPrivate::readMap() mMap.reset(); } else { // Try to load the tileset images for embedded tilesets - auto tilesets = mMap->tilesets(); - for (SharedTileset &tileset : tilesets) { - if (!tileset->isCollection() && tileset->fileName().isEmpty()) + for (const SharedTileset &tileset : mMap->tilesets()) { + if (tileset->fileName().isEmpty()) tileset->loadImage(); } @@ -1501,7 +1500,7 @@ std::unique_ptr MapReader::readMap(const QString &fileName) SharedTileset MapReader::readTileset(QIODevice *device, const QString &path) { SharedTileset tileset = d->readTileset(device, path); - if (tileset && !tileset->isCollection()) + if (tileset) tileset->loadImage(); return tileset; diff --git a/src/libtiled/mapwriter.cpp b/src/libtiled/mapwriter.cpp index 63758d2208..2025c5b14a 100644 --- a/src/libtiled/mapwriter.cpp +++ b/src/libtiled/mapwriter.cpp @@ -404,12 +404,17 @@ void MapWriterPrivate::writeTileset(QXmlStreamWriter &w, const Tileset &tileset, writeProperties(w, tileset.properties()); // Write the image element - const QUrl &imageSource = tileset.imageSource(); - if (!imageSource.isEmpty()) { + const bool isCollection = tileset.isCollection(); + if (!isCollection) { w.writeStartElement(QStringLiteral("image")); - QString source = toFileReference(imageSource, mUseAbsolutePaths ? QString() - : mDir.path()); - w.writeAttribute(QStringLiteral("source"), source); + + const QUrl &imageSource = tileset.imageSource(); + if (!imageSource.isEmpty()) { + // Write a reference to an external tileset image + QString source = toFileReference(imageSource, mUseAbsolutePaths ? QString() + : mDir.path()); + w.writeAttribute(QStringLiteral("source"), source); + } const QColor transColor = tileset.transparentColor(); if (transColor.isValid()) @@ -422,10 +427,22 @@ void MapWriterPrivate::writeTileset(QXmlStreamWriter &w, const Tileset &tileset, w.writeAttribute(QStringLiteral("height"), QString::number(tileset.imageHeight())); + if (imageSource.isEmpty()) { + // Write an embedded image + w.writeAttribute(QStringLiteral("format"), QLatin1String("png")); + + w.writeStartElement(QStringLiteral("data")); + w.writeAttribute(QStringLiteral("encoding"), QLatin1String("base64")); + + QBuffer buffer; + tileset.image().save(&buffer, "png"); + w.writeCharacters(QString::fromLatin1(buffer.data().toBase64())); + w.writeEndElement(); // + } + w.writeEndElement(); } - const bool isCollection = tileset.isCollection(); const bool includeAllTiles = isCollection || tileset.anyTileOutOfOrder(); for (const Tile *tile : tileset.tiles()) { diff --git a/src/libtiled/tileset.cpp b/src/libtiled/tileset.cpp index ea27a7b9f8..0f2564b52e 100644 --- a/src/libtiled/tileset.cpp +++ b/src/libtiled/tileset.cpp @@ -228,31 +228,29 @@ bool Tileset::loadFromImage(const QString &fileName) } /** - * Tries to load the image this tileset is referring to. + * Tries to load the image this tileset is referring to, if any. * * @return true if loading was successful, otherwise * returns false */ bool Tileset::loadImage() { - if (mTileWidth <= 0 || mTileHeight <= 0) { - mImageReference.status = LoadingError; - return false; - } - - mImage = ImageCache::loadPixmap(Tiled::urlToLocalFileOrQrc(mImageReference.source)); - if (mImage.isNull()) { - mImageReference.status = LoadingError; - return false; + if (mImageReference.hasImage()) { + mImage = mImageReference.create(); + if (mImage.isNull()) { + mImageReference.status = LoadingError; + return false; + } } - initializeTilesetTiles(); - - return true; + return initializeTilesetTiles(); } -void Tileset::initializeTilesetTiles() +bool Tileset::initializeTilesetTiles() { + if (mImage.isNull() || mTileWidth <= 0 || mTileHeight <= 0) + return false; + if (mImageReference.transparentColor.isValid()) mImage.setMask(mImage.createMaskFromColor(mImageReference.transparentColor)); @@ -294,6 +292,7 @@ void Tileset::initializeTilesetTiles() mImageReference.size = mImage.size(); mColumnCount = columnCountForWidth(mImageReference.size.width()); mImageReference.status = LoadingReady; + return true; } /** diff --git a/src/libtiled/tileset.h b/src/libtiled/tileset.h index d00602a4c6..c7b5b01156 100644 --- a/src/libtiled/tileset.h +++ b/src/libtiled/tileset.h @@ -198,6 +198,7 @@ class TILEDSHARED_EXPORT Tileset : public Object, public QEnableSharedFromThis &tilesets) const; @@ -293,8 +294,6 @@ class TILEDSHARED_EXPORT Tileset : public Object, public QEnableSharedFromThis &Tileset::wangSets() const diff --git a/src/libtiled/tilesetmanager.cpp b/src/libtiled/tilesetmanager.cpp index 11f3a578f9..fa97cdcef3 100644 --- a/src/libtiled/tilesetmanager.cpp +++ b/src/libtiled/tilesetmanager.cpp @@ -151,7 +151,7 @@ void TilesetManager::reloadImages(Tileset *tileset) } } emit tilesetImagesChanged(tileset); - } else { + } else if (tileset->imageSource().isLocalFile()) { ImageCache::remove(tileset->imageSource().toLocalFile()); if (tileset->loadImage()) emit tilesetImagesChanged(tileset); diff --git a/src/libtiled/varianttomapconverter.cpp b/src/libtiled/varianttomapconverter.cpp index 1ed449a227..2c2b9bf3b5 100644 --- a/src/libtiled/varianttomapconverter.cpp +++ b/src/libtiled/varianttomapconverter.cpp @@ -125,9 +125,8 @@ std::unique_ptr VariantToMapConverter::toMap(const QVariant &variant, } // Try to load the tileset images - auto tilesets = map->tilesets(); - for (SharedTileset &tileset : tilesets) { - if (!tileset->imageSource().isEmpty() && tileset->fileName().isEmpty()) + for (const SharedTileset &tileset : map->tilesets()) { + if (tileset->fileName().isEmpty()) tileset->loadImage(); } @@ -145,7 +144,7 @@ SharedTileset VariantToMapConverter::toTileset(const QVariant &variant, mReadingExternalTileset = true; SharedTileset tileset = toTileset(variant); - if (tileset && !tileset->imageSource().isEmpty()) + if (tileset) tileset->loadImage(); mReadingExternalTileset = false; diff --git a/src/tiled/editabletileset.cpp b/src/tiled/editabletileset.cpp index 4d6c4628f1..f5d05b1d9a 100644 --- a/src/tiled/editabletileset.cpp +++ b/src/tiled/editabletileset.cpp @@ -248,9 +248,7 @@ void EditableTileset::setImage(const QString &imageFilePath) push(new ChangeTilesetParameters(doc, parameters)); } else if (!checkReadOnly()) { tileset()->setImageSource(imageFilePath); - - if (!tileSize().isEmpty() && !image().isEmpty()) - tileset()->loadImage(); + tileset()->loadImage(); } } @@ -268,9 +266,43 @@ void EditableTileset::setTileSize(QSize size) push(new ChangeTilesetParameters(doc, parameters)); } else if (!checkReadOnly()) { tileset()->setTileSize(size); + tileset()->initializeTilesetTiles(); + } +} + +void EditableTileset::setTileSpacing(int tileSpacing) +{ + if (isCollection() && tileCount() > 0) { + ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Can't set tile spacing on an image collection tileset")); + return; + } + + if (auto doc = tilesetDocument()) { + TilesetParameters parameters(*tileset()); + parameters.tileSpacing = tileSpacing; - if (!tileSize().isEmpty() && !image().isEmpty()) - tileset()->loadImage(); + push(new ChangeTilesetParameters(doc, parameters)); + } else if (!checkReadOnly()) { + tileset()->setTileSpacing(tileSpacing); + tileset()->initializeTilesetTiles(); + } +} + +void EditableTileset::setMargin(int margin) +{ + if (isCollection() && tileCount() > 0) { + ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Can't set margin on an image collection tileset")); + return; + } + + if (auto doc = tilesetDocument()) { + TilesetParameters parameters(*tileset()); + parameters.margin = margin; + + push(new ChangeTilesetParameters(doc, parameters)); + } else if (!checkReadOnly()) { + tileset()->setMargin(margin); + tileset()->initializeTilesetTiles(); } } @@ -336,9 +368,7 @@ void EditableTileset::setTransparentColor(const QColor &color) push(new ChangeTilesetParameters(doc, parameters)); } else if (!checkReadOnly()) { tileset()->setTransparentColor(color); - - if (!tileSize().isEmpty() && !image().isEmpty()) - tileset()->loadImage(); + tileset()->initializeTilesetTiles(); } } diff --git a/src/tiled/editabletileset.h b/src/tiled/editabletileset.h index b82112cf76..8db0a30fe8 100644 --- a/src/tiled/editabletileset.h +++ b/src/tiled/editabletileset.h @@ -47,8 +47,8 @@ class EditableTileset : public EditableAsset Q_PROPERTY(int imageWidth READ imageWidth) Q_PROPERTY(int imageHeight READ imageHeight) Q_PROPERTY(QSize imageSize READ imageSize) - Q_PROPERTY(int tileSpacing READ tileSpacing) - Q_PROPERTY(int margin READ margin) + Q_PROPERTY(int tileSpacing READ tileSpacing WRITE setTileSpacing) + Q_PROPERTY(int margin READ margin WRITE setMargin) Q_PROPERTY(Alignment objectAlignment READ objectAlignment WRITE setObjectAlignment) Q_PROPERTY(TileRenderSize tileRenderSize READ tileRenderSize WRITE setTileRenderSize) Q_PROPERTY(FillMode fillMode READ fillMode WRITE setFillMode) @@ -157,6 +157,8 @@ public slots: void setTileHeight(int height); void setTileSize(QSize size); void setTileSize(int width, int height); + void setTileSpacing(int tileSpacing); + void setMargin(int margin); void setColumnCount(int columnCount); void setObjectAlignment(Alignment objectAlignment); void setTileRenderSize(TileRenderSize tileRenderSize);