[lua] Fix crash when saving tilemap's cel image (fix #4069)

This commit is contained in:
Martín Capello 2023-10-06 10:36:42 -03:00 committed by David Capello
parent 4a22518c3d
commit 3cfa5ef1da
6 changed files with 108 additions and 16 deletions

View File

@ -454,10 +454,29 @@ int Image_saveAs(lua_State* L)
return luaL_error(L, "script doesn't have access to write file %s",
absFn.c_str());
std::unique_ptr<Sprite> sprite(Sprite::MakeStdSprite(img->spec(), 256));
std::unique_ptr<Sprite> sprite;
std::vector<ImageRef> oneImage;
sprite->getImages(oneImage);
// If we are saving a tilemap's image, we create a sprite with a tilemap layer
// and the cel's layer tileset.
// TODO: Consider the possibility to add a "tileset" parameter to
// Image:saveAs{}.
if (cel && cel->layer()->isTilemap()) {
auto tileset = static_cast<LayerTilemap*>(cel->layer())->tileset();
auto spec = cel->sprite()->spec();
// Use the correct final image size, not the sprite size.
spec.setSize(tileset->grid().tilemapSizeToCanvas(img->spec().size()));
sprite.reset(Sprite::MakeStdTilemapSpriteWithTileset(spec,
img->spec(),
*tileset,
256));
sprite->getTilemapsByTileset(sprite->tilesets()->get(0), oneImage);
}
// Create a standard sprite otherwise
else {
sprite.reset(Sprite::MakeStdSprite(img->spec(), 256));
sprite->getImages(oneImage);
}
ASSERT(oneImage.size() == 1);
if (!oneImage.empty())
copy_image(oneImage.front().get(), img);

View File

@ -29,6 +29,7 @@
#include <algorithm>
#include <cstring>
#include <functional>
#include <memory>
#include <vector>
@ -119,23 +120,18 @@ Sprite::~Sprite()
}
}
// static
Sprite* Sprite::MakeStdSprite(const ImageSpec& spec,
const int ncolors,
const ImageBufferPtr& imageBuf)
static Sprite* makeSprite(const ImageSpec& spec,
const ImageRef& image,
std::function<std::unique_ptr<LayerImage>(Sprite* sprite)> makeLayer,
const int ncolors,
const ImageBufferPtr& imageBuf)
{
// Create the sprite.
std::unique_ptr<Sprite> sprite(new Sprite(spec, ncolors));
std::unique_ptr<Sprite> sprite = std::make_unique<Sprite>(spec, ncolors);
sprite->setTotalFrames(frame_t(1));
// Create the main image.
ImageRef image(Image::create(spec, imageBuf));
clear_image(image.get(), 0);
// Create the first transparent layer.
// Create the first layer.
{
std::unique_ptr<LayerImage> layer(new LayerImage(sprite.get()));
layer->setName("Layer 1");
std::unique_ptr<LayerImage> layer = makeLayer(sprite.get());
// Create the cel.
{
@ -154,6 +150,50 @@ Sprite* Sprite::MakeStdSprite(const ImageSpec& spec,
return sprite.release();
}
// static
Sprite* Sprite::MakeStdSprite(const ImageSpec& spec,
const int ncolors,
const ImageBufferPtr& imageBuf)
{
// Create the main image.
ImageRef image(Image::create(spec, imageBuf));
clear_image(image.get(), 0);
auto makeLayer = [](Sprite* sprite) {
// Create a transparent layer.
auto layer = std::make_unique<LayerImage>(sprite);
layer->setName("Layer 1");
return layer;
};
return makeSprite(spec, image, makeLayer, ncolors, imageBuf);
}
// static
Sprite* Sprite::MakeStdTilemapSpriteWithTileset(const ImageSpec& spec,
const ImageSpec& tilemapspec,
const Tileset& tileset,
const int ncolors,
const ImageBufferPtr& imageBuf)
{
ASSERT(spec.colorMode() != ColorMode::TILEMAP);
ASSERT(tilemapspec.colorMode() == ColorMode::TILEMAP);
ImageRef image(Image::create(tilemapspec, imageBuf));
clear_image(image.get(), 0);
auto makeLayer = [&tileset](Sprite* sprite) {
// Create a tilemap layer.
// We first need to add the tileset to the sprite.
sprite->tilesets()->add(
Tileset::MakeCopyCopyingImages(&tileset));
auto layer = std::make_unique<LayerTilemap>(sprite, 0);
layer->setName("Tilemap 1");
return layer;
};
return makeSprite(spec, image, makeLayer, ncolors, imageBuf);
}
//////////////////////////////////////////////////////////////////////
// Main properties

View File

@ -73,6 +73,14 @@ namespace doc {
static Sprite* MakeStdSprite(const ImageSpec& spec,
const int ncolors = 256,
const ImageBufferPtr& imageBuf = ImageBufferPtr());
// Creates a new sprite with one tilemap layer and one cel
// with a tilemap's image of the size specified by tilemapspec and the
// given tileset.
static Sprite* MakeStdTilemapSpriteWithTileset(const ImageSpec& spec,
const ImageSpec& tilemapspec,
const Tileset& tileset,
const int ncolors = 256,
const ImageBufferPtr& imageBuf = ImageBufferPtr());
////////////////////////////////////////
// Main properties

View File

@ -179,6 +179,29 @@ do
end
end
-- Save image from a tilemap's cel
do
local spr = Sprite{ fromFile="sprites/2x2tilemap2x2tile.aseprite" }
local tilemapImg = spr.layers[1].cels[1].image
local tileset = spr.layers[1].tileset
local tileSize = tileset.grid.tileSize
tilemapImg:saveAs("_test_save_tilemap_cel_image.png")
local img = Image{ fromFile="_test_save_tilemap_cel_image.png" }
assert(img.width == tilemapImg.width * tileSize.width)
assert(img.height == tilemapImg.height * tilemapImg.height)
for y=0,img.height-1 do
for x=0,img.width-1 do
local tmx = x // tileSize.w;
local tmy = y // tileSize.h;
local tileImg = tileset:getTile(tilemapImg:getPixel(tmx, tmy));
-- Compare each pixel of the saved image with each pixel of the
-- corresponding tile of the original sprite's tilemap.
assert(img:getPixel(x, y) == tileImg:getPixel(x % tileSize.w, y % tileSize.h))
end
end
end
-- Resize image
do
local a = Sprite(3, 2)

Binary file not shown.

View File

@ -33,3 +33,5 @@
* `slices-moving.aseprite`: Indexed, 4x4, 1 linked cel in 4 frames,
background layer, 1 slice with 4 keyframes (each keyframe with a
different position/size).
* `2x2tilemap2x2tile.aseprite`: RGB, 6x6, 2x2 tilemap layer, 5 tiles tileset,
2x2 tile size, 1 frame.