Add "external files chunk" to .aseprite files

This will be a way to reference the same external file from other file
through IDs (instead of using filenames on each reference).
This commit is contained in:
David Capello 2019-11-15 15:57:27 -03:00
parent c98c931227
commit 12becdaf45
7 changed files with 177 additions and 33 deletions

View File

@ -244,11 +244,23 @@ Color profile for RGB or grayscale values.
this fixed gamma, because sRGB uses different gamma sections
(linear and non-linear). If sRGB is specified with a fixed
gamma = 1.0, it means that this is Linear sRGB.
BYTE[8] Reserved (set to zero]
BYTE[8] Reserved (set to zero)
+ If type = ICC:
DWORD ICC profile data length
BYTE[] ICC profile data. More info: http://www.color.org/ICC1V42.pdf
### External Files Chunk (0x2008)
A list of external files linked with this file. It might be used to
reference external palettes or tilesets.
DWORD Number of entries
BYTE[8] Reserved (set to zero)
+ For each entry
DWORD Entry ID (this ID is referenced by tilesets or palettes)
BYTE[8] Reserved (set to zero)
STRING External file name
### Mask Chunk (0x2016) DEPRECATED
SHORT X position
@ -351,7 +363,8 @@ belongs to that cel, etc.
BYTE[16] Reserved
STRING Name of the tileset
+ If flag 1 is set
STRING Name of the external file
DWORD ID of the external file. This ID is one entry
of the the External Files Chunk.
DWORD Tileset ID in the external file
+ If flag 2 is set
DWORD Compressed data length

View File

@ -179,11 +179,17 @@ static void ase_file_write_slice_chunks(FILE* f, dio::AsepriteFrameHeader* frame
static void ase_file_write_slice_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, Slice* slice,
const frame_t fromFrame, const frame_t toFrame);
static void ase_file_write_user_data_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, const UserData* userData);
static void ase_file_write_tileset_chunks(FILE* f,
static void ase_file_write_external_files_chunk(FILE* f,
dio::AsepriteFrameHeader* frame_header,
dio::AsepriteExternalFiles& ext_files,
const Sprite* sprite);
static void ase_file_write_tileset_chunks(FILE* f, FileOp* fop,
dio::AsepriteFrameHeader* frame_header,
const dio::AsepriteExternalFiles& ext_files,
const Tilesets* tilesets);
static void ase_file_write_tileset_chunk(FILE* f,
static void ase_file_write_tileset_chunk(FILE* f, FileOp* fop,
dio::AsepriteFrameHeader* frame_header,
const dio::AsepriteExternalFiles& ext_files,
const Tileset* tileset,
const tileset_index si);
static bool ase_has_groups(LayerGroup* group);
@ -333,6 +339,7 @@ bool AseFormat::onSave(FileOp* fop)
// Write frames
int outputFrame = 0;
dio::AsepriteExternalFiles ext_files;
for (frame_t frame : fop->roi().selectedFrames()) {
// Prepare the frame header
dio::AsepriteFrameHeader frame_header;
@ -341,9 +348,14 @@ bool AseFormat::onSave(FileOp* fop)
// Frame duration
frame_header.duration = sprite->frameDuration(frame);
// Save color profile in first frame
if (outputFrame == 0 && fop->preserveColorProfile())
ase_file_write_color_profile(f, &frame_header, sprite);
if (outputFrame == 0) {
// Check if we need the "external files" chunk
ase_file_write_external_files_chunk(f, &frame_header, ext_files, sprite);
// Save color profile in first frame
if (fop->preserveColorProfile())
ase_file_write_color_profile(f, &frame_header, sprite);
}
// is the first frame or did the palette change?
Palette* pal = sprite->palette(frame);
@ -365,7 +377,8 @@ bool AseFormat::onSave(FileOp* fop)
// Write extra chunks in the first frame
if (frame == fop->roi().fromFrame()) {
// Write tilesets
ase_file_write_tileset_chunks(f, &frame_header, sprite->tilesets());
ase_file_write_tileset_chunks(f, fop, &frame_header, ext_files,
sprite->tilesets());
// Write layer chunks
for (Layer* child : sprite->root()->layers())
@ -1199,42 +1212,99 @@ static void ase_file_write_slice_chunk(FILE* f, dio::AsepriteFrameHeader* frame_
}
}
static void ase_file_write_tileset_chunks(FILE* f,
static void ase_file_write_external_files_chunk(
FILE* f,
dio::AsepriteFrameHeader* frame_header,
dio::AsepriteExternalFiles& ext_files,
const Sprite* sprite)
{
for (const Tileset* tileset : *sprite->tilesets()) {
if (!tileset->externalFilename().empty()) {
auto id = ++ext_files.lastid;
auto fn = tileset->externalFilename();
ext_files.to_fn[id] = fn;
ext_files.to_id[fn] = id;
}
}
// No external files to write
if (ext_files.lastid == 0)
return;
fputl(ext_files.to_fn.size(), f); // Number of entries
ase_file_write_padding(f, 8);
for (auto item : ext_files.to_fn) {
fputl(item.first, f); // ID
ase_file_write_padding(f, 8);
ase_file_write_string(f, item.second); // Filename
}
}
static void ase_file_write_tileset_chunks(FILE* f, FileOp* fop,
dio::AsepriteFrameHeader* frame_header,
const dio::AsepriteExternalFiles& ext_files,
const Tilesets* tilesets)
{
tileset_index si = 0;
for (const Tileset* tileset : *tilesets) {
ase_file_write_tileset_chunk(f, frame_header, tileset, si);
ase_file_write_tileset_chunk(f, fop, frame_header, ext_files,
tileset, si);
++si;
}
}
static void ase_file_write_tileset_chunk(FILE* f,
static void ase_file_write_tileset_chunk(FILE* f, FileOp* fop,
dio::AsepriteFrameHeader* frame_header,
const dio::AsepriteExternalFiles& ext_files,
const Tileset* tileset,
const tileset_index si)
{
ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_TILESET);
int flags = 0;
if (!tileset->externalFilename().empty())
flags |= ASE_TILESET_FLAG_EXTERNAL_FILE;
else
flags |= ASE_TILESET_FLAG_EMBEDDED;
fputl(si, f); // Tileset ID
fputl(2, f); // Tileset Flags (2=include tiles inside file)
fputl(flags, 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, 16);
ase_file_write_string(f, tileset->name()); // tileset name
// Flag 2 = tileset
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());
// Flag 1 = external tileset
if (flags & ASE_TILESET_FLAG_EXTERNAL_FILE) {
auto it = ext_files.to_id.find(tileset->externalFilename());
if (it != ext_files.to_id.end()) {
auto file_id = it->second;
fputl(file_id, f);
fputl(tileset->externalTileset(), f);
}
else {
ASSERT(false); // Impossible state (corrupted memory or we
// forgot to add the tileset external file to
// "ext_files")
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);
fputl(0, f);
fputl(0, f);
fop->setError("Error writing tileset external reference.\n");
}
}
// Flag 2 = tileset
if (flags & ASE_TILESET_FLAG_EMBEDDED) {
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());
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

@ -9,6 +9,9 @@
#define DIO_ASEPRITE_COMMON_H_INCLUDED
#pragma once
#include <map>
#include <string>
#define ASE_FILE_MAGIC 0xA5E0
#define ASE_FILE_FRAME_MAGIC 0xF1FA
@ -20,6 +23,7 @@
#define ASE_FILE_CHUNK_CEL 0x2005
#define ASE_FILE_CHUNK_CEL_EXTRA 0x2006
#define ASE_FILE_CHUNK_COLOR_PROFILE 0x2007
#define ASE_FILE_CHUNK_EXTERNAL_FILE 0x2008
#define ASE_FILE_CHUNK_MASK 0x2016
#define ASE_FILE_CHUNK_PATH 0x2017
#define ASE_FILE_CHUNK_TAGS 0x2018
@ -54,6 +58,9 @@
#define ASE_SLICE_FLAG_HAS_CENTER_BOUNDS 1
#define ASE_SLICE_FLAG_HAS_PIVOT_POINT 2
#define ASE_TILESET_FLAG_EXTERNAL_FILE 1
#define ASE_TILESET_FLAG_EMBEDDED 2
namespace dio {
struct AsepriteHeader {
@ -92,6 +99,12 @@ struct AsepriteChunk {
int start;
};
struct AsepriteExternalFiles {
std::map<uint32_t, std::string> to_fn; // ID -> filename
std::map<std::string, uint32_t> to_id; // filename -> ID
uint32_t lastid = 0;
};
} // namespace dio
#endif

View File

@ -68,6 +68,7 @@ bool AsepriteDecoder::decode()
doc::Cel* last_cel = nullptr;
int current_level = -1;
doc::LayerList allLayers;
AsepriteExternalFiles extFiles;
// Just one frame?
doc::frame_t nframes = sprite->totalFrames();
@ -163,6 +164,10 @@ bool AsepriteDecoder::decode()
break;
}
case ASE_FILE_CHUNK_EXTERNAL_FILE:
readExternalFiles(extFiles);
break;
case ASE_FILE_CHUNK_MASK: {
doc::Mask* mask = readMaskChunk();
if (mask)
@ -201,7 +206,7 @@ bool AsepriteDecoder::decode()
}
case ASE_FILE_CHUNK_TILESET: {
readTilesetChunk(sprite.get(), &header);
readTilesetChunk(sprite.get(), &header, extFiles);
break;
}
@ -837,6 +842,19 @@ void AsepriteDecoder::readColorProfile(doc::Sprite* sprite)
sprite->setColorSpace(cs);
}
void AsepriteDecoder::readExternalFiles(AsepriteExternalFiles& extFiles)
{
uint32_t n = read32();
readPadding(8);
for (uint32_t i=0; i<n; ++i) {
uint32_t id = read32();
readPadding(8);
std::string fn = readString();
extFiles.to_fn[id] = fn;
extFiles.to_id[fn] = id;
}
}
doc::Mask* AsepriteDecoder::readMaskChunk()
{
int c, u, v, byte;
@ -974,7 +992,8 @@ doc::Slice* AsepriteDecoder::readSliceChunk(doc::Slices& slices)
}
void AsepriteDecoder::readTilesetChunk(doc::Sprite* sprite,
const AsepriteHeader* header)
const AsepriteHeader* header,
const AsepriteExternalFiles& extFiles)
{
const doc::tileset_index id = read32();
const uint32_t flags = read32();
@ -992,18 +1011,27 @@ void AsepriteDecoder::readTilesetChunk(doc::Sprite* sprite,
return;
}
if (flags & 1) {
const std::string fn = readString(); // external filename
const doc::tileset_index externalId = read32(); // tileset ID in the external file
doc::Grid grid(gfx::Size(w, h));
auto tileset = new doc::Tileset(sprite, grid, ntiles);
tileset->setName(name);
// TODO add support to load the external filename
if (flags & ASE_TILESET_FLAG_EXTERNAL_FILE) {
const uint32_t extFileId = read32(); // filename ID in the external files chunk
const doc::tileset_index extTilesetId = read32(); // tileset ID in the external file
auto it = extFiles.to_fn.find(extFileId);
if (it != extFiles.to_fn.end()) {
auto fn = it->second;
tileset->setExternal(fn, extTilesetId);
}
else {
delegate()->error(
fmt::format("Error: Invalid external file reference (id={0} not found)",
extFileId));
}
}
if (flags & 2) {
doc::Grid grid(gfx::Size(w, h));
auto tileset = new doc::Tileset(sprite, grid, ntiles);
tileset->setName(name);
if (flags & ASE_TILESET_FLAG_EMBEDDED) {
const size_t dataSize = read32(); // Size of compressed data
const size_t dataBeg = f()->tell();
const size_t dataEnd = dataBeg+dataSize;

View File

@ -32,6 +32,7 @@ namespace dio {
struct AsepriteHeader;
struct AsepriteFrameHeader;
struct AsepriteExternalFiles;
class AsepriteDecoder : public Decoder {
public:
@ -54,13 +55,15 @@ private:
const size_t chunk_end);
void readCelExtraChunk(doc::Cel* cel);
void readColorProfile(doc::Sprite* sprite);
void readExternalFiles(AsepriteExternalFiles& extFiles);
doc::Mask* readMaskChunk();
void readTagsChunk(doc::Tags* tags);
void readSlicesChunk(doc::Slices& slices);
doc::Slice* readSliceChunk(doc::Slices& slices);
void readUserDataChunk(doc::UserData* userData);
void readTilesetChunk(doc::Sprite* sprite,
const AsepriteHeader* header);
const AsepriteHeader* header,
const AsepriteExternalFiles& extFiles);
};
} // namespace dio

View File

@ -42,4 +42,11 @@ void Tileset::resize(const tile_index ntiles)
m_tiles.resize(ntiles);
}
void Tileset::setExternal(const std::string& filename,
const tileset_index& tsi)
{
m_external.filename = filename;
m_external.tileset = tsi;
}
} // namespace doc

View File

@ -71,11 +71,21 @@ namespace doc {
m_tiles.erase(m_tiles.begin()+ti);
}
// Linked with an external file
void setExternal(const std::string& filename,
const tileset_index& tsi);
const std::string& externalFilename() const { return m_external.filename; }
tileset_index externalTileset() const { return m_external.tileset; }
private:
Sprite* m_sprite;
Grid m_grid;
Tiles m_tiles;
std::string m_name;
struct External {
std::string filename;
tileset_index tileset;
} m_external;
};
} // namespace doc