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 this fixed gamma, because sRGB uses different gamma sections
(linear and non-linear). If sRGB is specified with a fixed (linear and non-linear). If sRGB is specified with a fixed
gamma = 1.0, it means that this is Linear sRGB. 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: + If type = ICC:
DWORD ICC profile data length DWORD ICC profile data length
BYTE[] ICC profile data. More info: http://www.color.org/ICC1V42.pdf 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 ### Mask Chunk (0x2016) DEPRECATED
SHORT X position SHORT X position
@ -351,7 +363,8 @@ belongs to that cel, etc.
BYTE[16] Reserved 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 DWORD ID of the external file. This ID is one entry
of the the External Files Chunk.
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 Compressed data length 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, static void ase_file_write_slice_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, Slice* slice,
const frame_t fromFrame, const frame_t toFrame); 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_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, dio::AsepriteFrameHeader* frame_header,
const dio::AsepriteExternalFiles& ext_files,
const Tilesets* tilesets); 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, dio::AsepriteFrameHeader* frame_header,
const dio::AsepriteExternalFiles& ext_files,
const Tileset* tileset, const Tileset* tileset,
const tileset_index si); const tileset_index si);
static bool ase_has_groups(LayerGroup* group); static bool ase_has_groups(LayerGroup* group);
@ -333,6 +339,7 @@ bool AseFormat::onSave(FileOp* fop)
// Write frames // Write frames
int outputFrame = 0; int outputFrame = 0;
dio::AsepriteExternalFiles ext_files;
for (frame_t frame : fop->roi().selectedFrames()) { for (frame_t frame : fop->roi().selectedFrames()) {
// Prepare the frame header // Prepare the frame header
dio::AsepriteFrameHeader frame_header; dio::AsepriteFrameHeader frame_header;
@ -341,9 +348,14 @@ bool AseFormat::onSave(FileOp* fop)
// Frame duration // Frame duration
frame_header.duration = sprite->frameDuration(frame); frame_header.duration = sprite->frameDuration(frame);
// Save color profile in first frame if (outputFrame == 0) {
if (outputFrame == 0 && fop->preserveColorProfile()) // Check if we need the "external files" chunk
ase_file_write_color_profile(f, &frame_header, sprite); 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? // is the first frame or did the palette change?
Palette* pal = sprite->palette(frame); Palette* pal = sprite->palette(frame);
@ -365,7 +377,8 @@ bool AseFormat::onSave(FileOp* fop)
// Write extra chunks in the first frame // Write extra chunks in the first frame
if (frame == fop->roi().fromFrame()) { if (frame == fop->roi().fromFrame()) {
// Write tilesets // 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 // Write layer chunks
for (Layer* child : sprite->root()->layers()) 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, dio::AsepriteFrameHeader* frame_header,
const dio::AsepriteExternalFiles& ext_files,
const Tilesets* tilesets) const Tilesets* tilesets)
{ {
tileset_index si = 0; tileset_index si = 0;
for (const Tileset* tileset : *tilesets) { 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; ++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, dio::AsepriteFrameHeader* frame_header,
const dio::AsepriteExternalFiles& ext_files,
const Tileset* tileset, const Tileset* tileset,
const tileset_index si) const tileset_index si)
{ {
ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_TILESET); 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(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); 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, 16); 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 1 = external tileset
size_t beg = ftell(f); if (flags & ASE_TILESET_FLAG_EXTERNAL_FILE) {
fputl(0, f); // Field for compressed data length (completed later) auto it = ext_files.to_id.find(tileset->externalFilename());
TilesetScanlines gen(tileset); if (it != ext_files.to_id.end()) {
write_compressed_image(f, &gen, tileset->sprite()->pixelFormat()); 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); fputl(0, f);
fseek(f, beg, SEEK_SET); fputl(0, f);
fputl(end-beg-4, f); // Save the compressed data length fop->setError("Error writing tileset external reference.\n");
fseek(f, end, SEEK_SET); }
}
// 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) static bool ase_has_groups(LayerGroup* group)

View File

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

View File

@ -68,6 +68,7 @@ bool AsepriteDecoder::decode()
doc::Cel* last_cel = nullptr; doc::Cel* last_cel = nullptr;
int current_level = -1; int current_level = -1;
doc::LayerList allLayers; doc::LayerList allLayers;
AsepriteExternalFiles extFiles;
// Just one frame? // Just one frame?
doc::frame_t nframes = sprite->totalFrames(); doc::frame_t nframes = sprite->totalFrames();
@ -163,6 +164,10 @@ bool AsepriteDecoder::decode()
break; break;
} }
case ASE_FILE_CHUNK_EXTERNAL_FILE:
readExternalFiles(extFiles);
break;
case ASE_FILE_CHUNK_MASK: { case ASE_FILE_CHUNK_MASK: {
doc::Mask* mask = readMaskChunk(); doc::Mask* mask = readMaskChunk();
if (mask) if (mask)
@ -201,7 +206,7 @@ bool AsepriteDecoder::decode()
} }
case ASE_FILE_CHUNK_TILESET: { case ASE_FILE_CHUNK_TILESET: {
readTilesetChunk(sprite.get(), &header); readTilesetChunk(sprite.get(), &header, extFiles);
break; break;
} }
@ -837,6 +842,19 @@ void AsepriteDecoder::readColorProfile(doc::Sprite* sprite)
sprite->setColorSpace(cs); 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() doc::Mask* AsepriteDecoder::readMaskChunk()
{ {
int c, u, v, byte; int c, u, v, byte;
@ -974,7 +992,8 @@ doc::Slice* AsepriteDecoder::readSliceChunk(doc::Slices& slices)
} }
void AsepriteDecoder::readTilesetChunk(doc::Sprite* sprite, void AsepriteDecoder::readTilesetChunk(doc::Sprite* sprite,
const AsepriteHeader* header) const AsepriteHeader* header,
const AsepriteExternalFiles& extFiles)
{ {
const doc::tileset_index id = read32(); const doc::tileset_index id = read32();
const uint32_t flags = read32(); const uint32_t flags = read32();
@ -992,18 +1011,27 @@ void AsepriteDecoder::readTilesetChunk(doc::Sprite* sprite,
return; return;
} }
if (flags & 1) { doc::Grid grid(gfx::Size(w, h));
const std::string fn = readString(); // external filename auto tileset = new doc::Tileset(sprite, grid, ntiles);
const doc::tileset_index externalId = read32(); // tileset ID in the external file 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) { if (flags & ASE_TILESET_FLAG_EMBEDDED) {
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 dataSize = read32(); // Size of compressed data
const size_t dataBeg = f()->tell(); const size_t dataBeg = f()->tell();
const size_t dataEnd = dataBeg+dataSize; const size_t dataEnd = dataBeg+dataSize;

View File

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

View File

@ -42,4 +42,11 @@ void Tileset::resize(const tile_index ntiles)
m_tiles.resize(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 } // namespace doc

View File

@ -71,11 +71,21 @@ namespace doc {
m_tiles.erase(m_tiles.begin()+ti); 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: private:
Sprite* m_sprite; Sprite* m_sprite;
Grid m_grid; Grid m_grid;
Tiles m_tiles; Tiles m_tiles;
std::string m_name; std::string m_name;
struct External {
std::string filename;
tileset_index tileset;
} m_external;
}; };
} // namespace doc } // namespace doc