mirror of
https://github.com/aseprite/aseprite.git
synced 2024-09-02 21:19:10 +00:00
Change tileset chunk format saving the tileset as one image (one big column of tiles)
This commit is contained in:
parent
615fa33188
commit
c98c931227
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user