mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-23 22:43:32 +00:00
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:
parent
c98c931227
commit
12becdaf45
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user