-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Scripting: Unexpected results when using Image for tiles #3630
Comments
To aid in testing this stuff, here is the script I discovered most of these issues with. It adds an "Embed Images" action to the Tileset menu, which attempts to embed any external images into the tileset, using /* Embed Tileset Image by eishiya, last updated 21 Mar 2023
Adds an action to the Tileset and Map menus that lets you embed an image
into the tileset or map directly. This makes the tileset easier
to transfer to other devices, but harder to modify.
This script should NOT be used in production! Image Layer embedding does not
actually work, and Tileset image embedding does not work as expected.
In addition, most of these changes are not reversible except through further
scripting! Most custom formats can't access the embedded images, and most
Tiled map/tileset parsers can't read them.
Requires Tiled 1.10+ to work correctly with Image Collections, but should
work for other document types otherwise.
WARNING: For Image Collections, *new* tiles have to be created in order
to embed the image. This will break the tile IDs! If you use this script
on Image Collections, make sure you do it before you use the tiles anywhere.
*/
var embedImages = tiled.registerAction("EmbedImages", function(action) {
let asset = tiled.activeAsset;
if(!asset)
return;
if(asset.isTileset) {
if(asset.isCollection) {
let tiles = asset.tiles;
let tilesToRemove = [];
for(tile of tiles) {
let image = new Image(tile.imageFileName);
if(image && image.width * image.height > 0) {
//tile.setImage(image); //does nothing to existing tiles.
let newTile = asset.addTile();
newTile.imageRect = tile.imageRect;
newTile.setImage(image);
tilesToRemove.push(tile);
} else {
tiled.log("Warning: Tile "+tile.id+" in tileset '"+asset.name+"' has an invalid image file name. It may have already been embedded.");
}
}
asset.removeTiles(tilesToRemove);
} else { //image-based tileset;
if(!asset.image || asset.image.length < 1) {
tiled.error("Embed failed: This tileset has no image set.");
return;
}
let image = new Image(asset.image);
if(!image || image.width * image.height < 1) {
tiled.error("Embed failed: The image could not be loaded from file.");
return;
}
asset.loadFromImage(image);
}
} else if(asset.isTileMap) {
//tiled.warn("Embedding Image Layer images is not implemented yet.");
function embedImageLayers(layer) {
if(layer.isImageLayer) {
let path = layer.imageSource.toString().replace(/^(file:\/{3})/,"");
let image = new Image(path);
if(image && image.width * image.height > 0) {
//layer.setImage(image, path);
layer.setImage(image);
} else {
tiled.log("Warning: Image Layer "+layer.name+" has an invalid image file name. It may have already been embedded.");
}
} else if(layer.isTileMap || layer.isGroupLayer) {
for(let gi = 0; gi < layer.layerCount; ++gi) {
embedImageLayers(layer.layerAt(gi));
}
}
}
embedImageLayers(asset);
}
});
embedImages.text = "Embed Images";
tiled.extendMenu("Tileset", [
{ action: "EmbedImages", before: "TilesetProperties" },
{separator: true}
]);
tiled.extendMenu("Map", [
{ action: "EmbedImages", before: "MapProperties" },
{separator: true}
]); |
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 mapeditor#3630.
#3841 addressed part of the reported issues, but reading the list again, there are still some things that could be easily addressed, like the documentation for |
Usually one could load the image based on Tile.imageFileName, but when a tile's image has been set directly using Tile.setImage, this is not possible. This can happen for example when an importer has used this function since there was no external image file to refer to, or it used an unsupported image format. Tile.image now provides convenient access to the tile's image, falling back to the Tileset's image when the tile has no individual image. As such, this function also provides access to the tileset's image, which would ideally be available through Tileset.image, but this property was already taken and used for the image file name. To free up this property in the future it is now deprecated. Extensions should switch to the new Tileset.imageFileName property. Part of issue mapeditor#3630
Alright, going over the original list of issues:
Then regarding image layers:
|
@eishiya Btw, your script complains about my image layer, since its regexp doesn't work in my case:
It strips away one too many |
On Windows, you get e.g. Are QUrls ever actually useful in the Tiled API? Every path parameter seems to take a string path, so if possible, I think everything that returns a path should return a string path, not a QUrl. Do you want me to test the changes so far? I won't be able to today, but I can take a look another day. |
I guess not... it's just that internally
It would be a great help, but if not I'll look into testing things a little tomorrow. For these changes, testing is more work than the implementation. :-) |
I've tested this with the help of your script. The difference with the other The embedding does work if I add |
Now it also clears an existing file name, when no file name is specified. This way the function should behave more like expected, and more consistent with ImageLayer.setImage (which takes a URL however...) and Tileset.loadFromImage. Also fixed updating of the map view, when using Tileset.loadFromImage and Tile.setImage. Issue mapeditor#3630
Now it also clears an existing file name, when no file name is specified. This way the function should behave more like expected, and more consistent with ImageLayer.setImage (which takes a URL however...) and Tileset.loadFromImage. Also fixed updating of the map view, when using Tileset.loadFromImage and Tile.setImage. Issue mapeditor#3630
I think this is the only remaining issue I haven't looked into. If you can reproduce this, feel free to open a new issue about it. |
These are really several issues, but they're all about roughly the same thing: embedding image data directly into tilesets via scripting.
Tileset.loadFromImage(image)
creates an Image Collection in which every tile has the same source image, but uses different subrects of it. This is quite unexpected, I'd have thought this would set the Tileset's source image instead. Saving a Tileset created this way to a file duplicates the entire image (base64-encoded) for each tile. I'm guessing this is because there is no source image path and Tiled treats every tileset with no image path as an Image Collection, but this is one case where it should not.Tile.setImage()
requires that the tile'simageRect
be set first. If it's left at the default (presumably 0,0,0,0), setImage() seems to fail, and the tile is not written out when the Tileset is saved (in TSX; see (5) below).Tile.setImage()
warns that it "does not affect the saved tileset!". It does though, and the image data is embedded correctly (in TSX, anyway). What it doesn't affect is Tiles that have a source image path, as far as I can tell. So,setImage()
cannot be used to modify tiles that were created normally. I have not looked into what happens if one triessetImage()
on a tile that was created withsetImage()
:]Tile.setImage()
does not affect normal tiles, it means it cannot be used to replace the image of a tile. This means that if I want to create a script that embeds all the images of a tileset, I have to either create a whole new tileset and put the tiles there, or create new tiles and delete the original ones. Either way, tile IDs are likely to change, breaking existing maps. Some way to override an existing tile's image withsetImage()
would be nice.Tileset.loadFromImage
andTile.setImage
, so they'll have the same problem as TSJ.Edit: I also experimented with
ImageLayer.setImage
and ran into several issues:setImage
, you have to change document tabs and back to see the result.ImageLayer.imageSource
is a string, but it's some sort of object, probably a QUrl. It being a QUrl also means it needs to be processed before it can be used as a path.The text was updated successfully, but these errors were encountered: