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. - **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. - **Grayscale**: `BYTE[2]`, each pixel have 2 bytes in the order Value, Alpha.
- **Indexed**: `BYTE`, each pixel uses 1 byte (the index). - **Indexed**: `BYTE`, each pixel uses 1 byte (the index).
* `TILE`: **Tilemaps**: Each tile can be a 8-bit, 16-bit, or 32-bit * `TILE`: **Tilemaps**: Each tile can be a 8-bit (`BYTE`), 16-bit
value and there are masks related to the meaning of each bit. (`WORD`), or 32-bit (`DWORD`) value and there are masks related to
the meaning of each bit.
## Introduction ## 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 Note: valid only if file header flags field has bit 1 set
BYTE[3] For future (set to zero) BYTE[3] For future (set to zero)
STRING Layer name STRING Layer name
+ If layer type = 3 + If layer type = 2
DWORD Tileset index DWORD Tileset index
### Cel Chunk (0x2005) ### 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) + For cel type = 3 (Compressed Tilemap)
WORD Width in pixels WORD Width in pixels
WORD Height 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 tile ID (e.g. 0x1fffffff for 32-bit tiles)
DWORD Bitmask for X flip DWORD Bitmask for X flip
DWORD Bitmask for Y flip DWORD Bitmask for Y flip
@ -344,19 +345,18 @@ belongs to that cel, etc.
DWORD Tileset flags DWORD Tileset flags
1 - Include link to external file 1 - Include link to external file
2 - Include tiles inside this file 2 - Include tiles inside this file
WORD Tiles width DWORD Number of tiles
WORD Tiles height WORD Tile Width
BYTE[36] Reserved WORD Tile Height
BYTE[16] Reserved
STRING Name of the tileset STRING Name of the tileset
+ If flag 1 is set + 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 DWORD Tileset ID in the external file
+ If flag 2 is set + If flag 2 is set
DWORD Number of tiles to read DWORD Compressed data length
+ For each tile PIXEL[] Compressed Tileset image (see NOTE.3):
DWORD Tile flags (0) (Tile Width) x (Tile Height x Number of Tiles)
DWORD Compressed data length
PIXEL[] Read tile image (tile width x height compressed pixels, see NOTE.3)
### Notes ### Notes

View File

@ -80,6 +80,58 @@ private:
doc::Sprite* m_sprite; 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 } // anonymous namespace
static void ase_file_prepare_header(FILE* f, dio::AsepriteHeader* header, const Sprite* sprite, 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> 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; PixelIO<ImageTraits> pixel_io;
z_stream zstream; z_stream zstream;
@ -730,18 +782,19 @@ static void write_compressed_image_templ(FILE* f, const Image* image)
if (err != Z_OK) if (err != Z_OK)
throw base::Exception("ZLib error %d in deflateInit().", err); 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); 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 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.next_in = (Bytef*)&scanline[0];
zstream.avail_in = scanline.size(); 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 { do {
zstream.next_out = (Bytef*)&compressed[0]; 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); 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: case IMAGE_RGB:
write_compressed_image_templ<RgbTraits>(f, image); write_compressed_image_templ<RgbTraits>(f, gen);
break; break;
case IMAGE_GRAYSCALE: case IMAGE_GRAYSCALE:
write_compressed_image_templ<GrayscaleTraits>(f, image); write_compressed_image_templ<GrayscaleTraits>(f, gen);
break; break;
case IMAGE_INDEXED: case IMAGE_INDEXED:
write_compressed_image_templ<IndexedTraits>(f, image); write_compressed_image_templ<IndexedTraits>(f, gen);
break; break;
case IMAGE_TILEMAP: case IMAGE_TILEMAP:
write_compressed_image_templ<TilemapTraits>(f, image); write_compressed_image_templ<TilemapTraits>(f, gen);
break; break;
} }
} }
@ -873,7 +926,9 @@ static void ase_file_write_cel_chunk(FILE* f, dio::AsepriteFrameHeader* frame_he
// Width and height // Width and height
fputw(image->width(), f); fputw(image->width(), f);
fputw(image->height(), f); fputw(image->height(), f);
write_compressed_image(f, image);
ImageScanlines scan(image);
write_compressed_image(f, &scan, image->pixelFormat());
} }
else { else {
// Width and height // 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); fputl(tile_f_90cw, f);
ase_file_write_padding(f, 10); 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(si, f); // Tileset ID
fputl(2, f); // Tileset Flags (2=include tiles inside file) fputl(2, f); // Tileset Flags (2=include tiles inside file)
fputl(tileset->size(), f);
fputw(tileset->grid().tileSize().w, f); fputw(tileset->grid().tileSize().w, f);
fputw(tileset->grid().tileSize().h, 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 ase_file_write_string(f, tileset->name()); // tileset name
// Flag 2 = tileset // Flag 2 = tileset
fputl(tileset->size(), f); size_t beg = ftell(f);
for (tile_index i=0; i<tileset->size(); ++i) { fputl(0, f); // Field for compressed data length (completed later)
fputl(0, f); // Flags (zero) TilesetScanlines gen(tileset);
size_t beg = ftell(f); write_compressed_image(f, &gen, tileset->sprite()->pixelFormat());
fputl(0, f);
ImageRef tileImg = tileset->get(i); size_t end = ftell(f);
write_compressed_image(f, tileImg.get()); fseek(f, beg, SEEK_SET);
fputl(end-beg-4, f); // Save the compressed data length
size_t end = ftell(f); fseek(f, end, SEEK_SET);
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) static bool ase_has_groups(LayerGroup* group)

View File

@ -441,6 +441,10 @@ doc::Layer* AsepriteDecoder::readLayerChunk(AsepriteHeader* header,
case ASE_FILE_LAYER_TILEMAP: { case ASE_FILE_LAYER_TILEMAP: {
doc::tileset_index tsi = read32(); 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); layer = new doc::LayerTilemap(sprite, tsi);
break; break;
} }
@ -974,36 +978,43 @@ void AsepriteDecoder::readTilesetChunk(doc::Sprite* sprite,
{ {
const doc::tileset_index id = read32(); const doc::tileset_index id = read32();
const uint32_t flags = read32(); const uint32_t flags = read32();
const doc::tile_index ntiles = read32();
const int w = read16(); const int w = read16();
const int h = read16(); const int h = read16();
readPadding(20); readPadding(16);
const std::string name = readString(); 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) { if (flags & 1) {
// Ignore fields const std::string fn = readString(); // external filename
readString(); // external filename const doc::tileset_index externalId = read32(); // tileset ID in the external file
read32(); // tileset ID in the external file
// TODO add support to load the external filename
} }
if (flags & 2) { if (flags & 2) {
const doc::tile_index ntiles = read32();
doc::Grid grid(gfx::Size(w, h)); doc::Grid grid(gfx::Size(w, h));
auto tileset = new doc::Tileset(sprite, grid, ntiles); auto tileset = new doc::Tileset(sprite, grid, ntiles);
tileset->setName(name); 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) { for (doc::tile_index i=0; i<ntiles; ++i) {
read32(); // Flags (ignore) doc::ImageRef tile(doc::crop_image(alltiles.get(), 0, i*h, w, h, alltiles->maskColor()));
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);
tileset->set(i, tile); tileset->set(i, tile);
f()->seek(dataEnd);
} }
sprite->tilesets()->set(id, tileset); sprite->tilesets()->set(id, tileset);