mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-29 01:20:17 +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.
|
- **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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user