Change tileset chunk format saving the tileset as one image (one big column of tiles)

This commit is contained in:
David Capello 2019-11-08 16:50:49 -03:00
parent 615fa33188
commit c98c931227
3 changed files with 120 additions and 57 deletions

View File

@ -26,8 +26,9 @@ ASE files use Intel (little-endian) byte order.
- **RGBA**: `BYTE[4]`, each pixel have 4 bytes in this order Red, Green, Blue, Alpha.
- **Grayscale**: `BYTE[2]`, each pixel have 2 bytes in the order Value, Alpha.
- **Indexed**: `BYTE`, each pixel uses 1 byte (the index).
* `TILE`: **Tilemaps**: Each tile can be a 8-bit, 16-bit, or 32-bit
value and there are masks related to the meaning of each bit.
* `TILE`: **Tilemaps**: Each tile can be a 8-bit (`BYTE`), 16-bit
(`WORD`), or 32-bit (`DWORD`) value and there are masks related to
the meaning of each bit.
## Introduction
@ -176,7 +177,7 @@ Ignore this chunk if you find the new palette chunk (0x2019)
Note: valid only if file header flags field has bit 1 set
BYTE[3] For future (set to zero)
STRING Layer name
+ If layer type = 3
+ If layer type = 2
DWORD Tileset index
### Cel Chunk (0x2005)
@ -207,7 +208,7 @@ This chunk determine where to put a cel in the specified layer/frame.
+ For cel type = 3 (Compressed Tilemap)
WORD Width in pixels
WORD Height in pixels
WORD Bits per pixel/tile reference (8, 16, or 32)
WORD Bits per tile (8, 16, or 32)
DWORD Bitmask for tile ID (e.g. 0x1fffffff for 32-bit tiles)
DWORD Bitmask for X flip
DWORD Bitmask for Y flip
@ -344,19 +345,18 @@ belongs to that cel, etc.
DWORD Tileset flags
1 - Include link to external file
2 - Include tiles inside this file
WORD Tiles width
WORD Tiles height
BYTE[36] Reserved
DWORD Number of tiles
WORD Tile Width
WORD Tile Height
BYTE[16] Reserved
STRING Name of the tileset
+ If flag 1 is set
STRING Name of the external file (path relative to this file, in the best case)
STRING Name of the external file
DWORD Tileset ID in the external file
+ If flag 2 is set
DWORD Number of tiles to read
+ For each tile
DWORD Tile flags (0)
DWORD Compressed data length
PIXEL[] Read tile image (tile width x height compressed pixels, see NOTE.3)
DWORD Compressed data length
PIXEL[] Compressed Tileset image (see NOTE.3):
(Tile Width) x (Tile Height x Number of Tiles)
### Notes

View File

@ -80,6 +80,58 @@ private:
doc::Sprite* m_sprite;
};
class ScanlinesGen {
public:
virtual ~ScanlinesGen() { }
virtual gfx::Size getImageSize() = 0;
virtual int getScanlineSize() = 0;
virtual const uint8_t* getScanlineAddress(int y) = 0;
};
class ImageScanlines : public ScanlinesGen {
const Image* m_image;
public:
ImageScanlines(const Image* image) : m_image(image) { }
gfx::Size getImageSize() override {
return gfx::Size(m_image->width(),
m_image->height());
}
int getScanlineSize() override {
return doc::calculate_rowstride_bytes(
m_image->pixelFormat(),
m_image->width());
}
const uint8_t* getScanlineAddress(int y) override {
return m_image->getPixelAddress(0, y);
}
};
class TilesetScanlines : public ScanlinesGen {
const Tileset* m_tileset;
public:
TilesetScanlines(const Tileset* tileset) : m_tileset(tileset) { }
gfx::Size getImageSize() override {
return gfx::Size(m_tileset->grid().tileSize().w,
m_tileset->grid().tileSize().h * m_tileset->size());
}
int getScanlineSize() override {
return doc::calculate_rowstride_bytes(
m_tileset->sprite()->pixelFormat(),
m_tileset->grid().tileSize().w);
}
const uint8_t* getScanlineAddress(int y) override {
const int h = m_tileset->grid().tileSize().h;
const tile_index ti = (y / h);
ASSERT(ti >= 0 && ti < m_tileset->size());
ImageRef image = m_tileset->get(ti);
ASSERT(image);
if (image)
return image->getPixelAddress(0, y % h);
else
return nullptr;
}
};
} // anonymous namespace
static void ase_file_prepare_header(FILE* f, dio::AsepriteHeader* header, const Sprite* sprite,
@ -717,7 +769,7 @@ static void write_raw_image(FILE* f, const Image* image)
//////////////////////////////////////////////////////////////////////
template<typename ImageTraits>
static void write_compressed_image_templ(FILE* f, const Image* image)
static void write_compressed_image_templ(FILE* f, ScanlinesGen* gen)
{
PixelIO<ImageTraits> pixel_io;
z_stream zstream;
@ -730,18 +782,19 @@ static void write_compressed_image_templ(FILE* f, const Image* image)
if (err != Z_OK)
throw base::Exception("ZLib error %d in deflateInit().", err);
std::vector<uint8_t> scanline(ImageTraits::getRowStrideBytes(image->width()));
std::vector<uint8_t> scanline(gen->getScanlineSize());
std::vector<uint8_t> compressed(4096);
for (y=0; y<image->height(); y++) {
const gfx::Size imgSize = gen->getImageSize();
for (y=0; y<imgSize.h; ++y) {
typename ImageTraits::address_t address =
(typename ImageTraits::address_t)image->getPixelAddress(0, y);
(typename ImageTraits::address_t)gen->getScanlineAddress(y);
pixel_io.write_scanline(address, image->width(), &scanline[0]);
pixel_io.write_scanline(address, imgSize.w, &scanline[0]);
zstream.next_in = (Bytef*)&scanline[0];
zstream.avail_in = scanline.size();
int flush = (y == image->height()-1 ? Z_FINISH: Z_NO_FLUSH);
int flush = (y == imgSize.h-1 ? Z_FINISH: Z_NO_FLUSH);
do {
zstream.next_out = (Bytef*)&compressed[0];
@ -766,23 +819,23 @@ static void write_compressed_image_templ(FILE* f, const Image* image)
throw base::Exception("ZLib error %d in deflateEnd().", err);
}
static void write_compressed_image(FILE* f, const Image* image)
static void write_compressed_image(FILE* f, ScanlinesGen* gen, PixelFormat pixelFormat)
{
switch (image->pixelFormat()) {
switch (pixelFormat) {
case IMAGE_RGB:
write_compressed_image_templ<RgbTraits>(f, image);
write_compressed_image_templ<RgbTraits>(f, gen);
break;
case IMAGE_GRAYSCALE:
write_compressed_image_templ<GrayscaleTraits>(f, image);
write_compressed_image_templ<GrayscaleTraits>(f, gen);
break;
case IMAGE_INDEXED:
write_compressed_image_templ<IndexedTraits>(f, image);
write_compressed_image_templ<IndexedTraits>(f, gen);
break;
case IMAGE_TILEMAP:
write_compressed_image_templ<TilemapTraits>(f, image);
write_compressed_image_templ<TilemapTraits>(f, gen);
break;
}
}
@ -873,7 +926,9 @@ static void ase_file_write_cel_chunk(FILE* f, dio::AsepriteFrameHeader* frame_he
// Width and height
fputw(image->width(), f);
fputw(image->height(), f);
write_compressed_image(f, image);
ImageScanlines scan(image);
write_compressed_image(f, &scan, image->pixelFormat());
}
else {
// Width and height
@ -897,7 +952,8 @@ static void ase_file_write_cel_chunk(FILE* f, dio::AsepriteFrameHeader* frame_he
fputl(tile_f_90cw, f);
ase_file_write_padding(f, 10);
write_compressed_image(f, image);
ImageScanlines scan(image);
write_compressed_image(f, &scan, IMAGE_TILEMAP);
}
}
}
@ -1163,26 +1219,22 @@ static void ase_file_write_tileset_chunk(FILE* f,
fputl(si, f); // Tileset ID
fputl(2, f); // Tileset Flags (2=include tiles inside file)
fputl(tileset->size(), f);
fputw(tileset->grid().tileSize().w, f);
fputw(tileset->grid().tileSize().h, f);
ase_file_write_padding(f, 20);
ase_file_write_padding(f, 16);
ase_file_write_string(f, tileset->name()); // tileset name
// Flag 2 = tileset
fputl(tileset->size(), f);
for (tile_index i=0; i<tileset->size(); ++i) {
fputl(0, f); // Flags (zero)
size_t beg = ftell(f);
fputl(0, f);
size_t beg = ftell(f);
fputl(0, f); // Field for compressed data length (completed later)
TilesetScanlines gen(tileset);
write_compressed_image(f, &gen, tileset->sprite()->pixelFormat());
ImageRef tileImg = tileset->get(i);
write_compressed_image(f, tileImg.get());
size_t end = ftell(f);
fseek(f, beg, SEEK_SET);
fputl(end-beg-4, f); // Save the compressed data length
fseek(f, end, SEEK_SET);
}
size_t end = ftell(f);
fseek(f, beg, SEEK_SET);
fputl(end-beg-4, f); // Save the compressed data length
fseek(f, end, SEEK_SET);
}
static bool ase_has_groups(LayerGroup* group)

View File

@ -441,6 +441,10 @@ doc::Layer* AsepriteDecoder::readLayerChunk(AsepriteHeader* header,
case ASE_FILE_LAYER_TILEMAP: {
doc::tileset_index tsi = read32();
if (!sprite->tilesets()->get(tsi)) {
delegate()->error(fmt::format("Error: tileset {0} not found", tsi));
return nullptr;
}
layer = new doc::LayerTilemap(sprite, tsi);
break;
}
@ -974,36 +978,43 @@ void AsepriteDecoder::readTilesetChunk(doc::Sprite* sprite,
{
const doc::tileset_index id = read32();
const uint32_t flags = read32();
const doc::tile_index ntiles = read32();
const int w = read16();
const int h = read16();
readPadding(20);
readPadding(16);
const std::string name = readString();
// TODO add support to load the external filename
// Errors
if (ntiles < 1 || w < 1 || h < 1) {
delegate()->error(
fmt::format("Error: Invalid tileset (number of tiles={0}, tile size={1}x{2})",
ntiles, w, h));
return;
}
if (flags & 1) {
// Ignore fields
readString(); // external filename
read32(); // tileset ID in the external file
const std::string fn = readString(); // external filename
const doc::tileset_index externalId = read32(); // tileset ID in the external file
// TODO add support to load the external filename
}
if (flags & 2) {
const doc::tile_index ntiles = read32();
doc::Grid grid(gfx::Size(w, h));
auto tileset = new doc::Tileset(sprite, grid, ntiles);
tileset->setName(name);
const size_t dataSize = read32(); // Size of compressed data
const size_t dataBeg = f()->tell();
const size_t dataEnd = dataBeg+dataSize;
doc::ImageRef alltiles(doc::Image::create(sprite->pixelFormat(), w, h*ntiles));
read_compressed_image(f(), delegate(), alltiles.get(), header, dataEnd);
f()->seek(dataEnd);
for (doc::tile_index i=0; i<ntiles; ++i) {
read32(); // Flags (ignore)
const size_t dataSize = read32(); // Size of compressed data
const size_t dataBeg = f()->tell();
const size_t dataEnd = dataBeg+dataSize;
doc::ImageRef tile(doc::Image::create(sprite->pixelFormat(), w, h));
read_compressed_image(f(), delegate(), tile.get(), header, dataEnd);
doc::ImageRef tile(doc::crop_image(alltiles.get(), 0, i*h, w, h, alltiles->maskColor()));
tileset->set(i, tile);
f()->seek(dataEnd);
}
sprite->tilesets()->set(id, tileset);