mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-01 10:21:04 +00:00
commit
8f09728105
@ -17,11 +17,24 @@ ASE files use Intel (little-endian) byte order.
|
||||
* `DWORD`: A 32-bit unsigned integer value
|
||||
* `LONG`: A 32-bit signed integer value
|
||||
* `FIXED`: A 32-bit fixed point (16.16) value
|
||||
* `FLOAT`: A 32-bit single-precision value
|
||||
* `DOUBLE`: A 64-bit double-precision value
|
||||
* `QWORD`: A 64-bit unsigned integer value
|
||||
* `LONG64`: A 64-bit signed integer value
|
||||
* `BYTE[n]`: "n" bytes.
|
||||
* `STRING`:
|
||||
- `WORD`: string length (number of bytes)
|
||||
- `BYTE[length]`: characters (in UTF-8)
|
||||
The `'\0'` character is not included.
|
||||
* `POINT`:
|
||||
- `LONG`: X coordinate value
|
||||
- `LONG`: Y coordinate value
|
||||
* `SIZE`:
|
||||
- `LONG`: Width value
|
||||
- `LONG`: Height value
|
||||
* `RECT`:
|
||||
- `POINT`: Origin coordinates
|
||||
- `SIZE`: Rectangle size
|
||||
* `PIXEL`: One pixel, depending on the image pixel format:
|
||||
- **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.
|
||||
@ -251,15 +264,19 @@ Color profile for RGB or grayscale values.
|
||||
|
||||
### External Files Chunk (0x2008)
|
||||
|
||||
A list of external files linked with this file. It might be used to
|
||||
reference external palettes or tilesets.
|
||||
A list of external files linked with this file can be found in the first frame. It might be used to
|
||||
reference external palettes, tilesets, or extensions that make use of extended properties.
|
||||
|
||||
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
|
||||
DWORD Entry ID (this ID is referenced by tilesets, palettes, or extended properties)
|
||||
BYTE Type
|
||||
0 - External palette
|
||||
1 - External tileset
|
||||
2 - Extension name for properties
|
||||
BYTE[7] Reserved (set to zero)
|
||||
STRING External file name or extension ID
|
||||
|
||||
### Mask Chunk (0x2016) DEPRECATED
|
||||
|
||||
@ -331,13 +348,18 @@ layer, this user data belongs to that layer, if we've read a cel, it
|
||||
belongs to that cel, etc. There are some special cases: After a Tags
|
||||
chunk, there will be several user data fields, one for each tag, you
|
||||
should associate the user data in the same order as the tags are in
|
||||
the Tags chunk.
|
||||
the Tags chunk. Another special case is after the Tileset chunk, it
|
||||
could be followed by a user data chunk (empty or not) and then all
|
||||
the user data chunks of the tiles ordered by tile index, or it could
|
||||
be followed by none user data chunk if the file was created in an
|
||||
older Aseprite version.
|
||||
In version 1.3 a sprite has associated user data, to consider this case
|
||||
there is an User Data Chunk at the first frame after the Palette Chunk.
|
||||
|
||||
DWORD Flags
|
||||
1 = Has text
|
||||
2 = Has color
|
||||
4 = Has properties
|
||||
+ If flags have bit 1
|
||||
STRING Text
|
||||
+ If flags have bit 2
|
||||
@ -345,6 +367,59 @@ there is an User Data Chunk at the first frame after the Palette Chunk.
|
||||
BYTE Color Green (0-255)
|
||||
BYTE Color Blue (0-255)
|
||||
BYTE Color Alpha (0-255)
|
||||
+ If flags have bit 4
|
||||
DWORD Size in bytes of all properties maps stored in this chunk
|
||||
DWORD Number of properties maps
|
||||
+ For each properties map:
|
||||
DWORD Properties maps key
|
||||
== 0 means user properties
|
||||
!= 0 means an extension Entry ID (see External Files Chunk))
|
||||
DWORD Number of properties
|
||||
+ For each property:
|
||||
STRING Name
|
||||
WORD Type
|
||||
+ If type==0x0001 (bool)
|
||||
BYTE == 0 means FALSE
|
||||
!= 0 means TRUE
|
||||
+ If type==0x0002 (int8)
|
||||
BYTE
|
||||
+ If type==0x0003 (uint8)
|
||||
BYTE
|
||||
+ If type==0x0004 (int16)
|
||||
SHORT
|
||||
+ If type==0x0005 (uint16)
|
||||
WORD
|
||||
+ If type==0x0006 (int32)
|
||||
LONG
|
||||
+ If type==0x0007 (uint32)
|
||||
DWORD
|
||||
+ If type==0x0008 (int64)
|
||||
LONG64
|
||||
+ If type==0x0009 (uint64)
|
||||
QWORD
|
||||
+ If type==0x000A
|
||||
FIXED
|
||||
+ If type==0x000B
|
||||
FLOAT
|
||||
+ If type==0x000C
|
||||
DOUBLE
|
||||
+ If type==0x000D
|
||||
STRING
|
||||
+ If type==0x000E
|
||||
POINT
|
||||
+ If type==0x000F
|
||||
SIZE
|
||||
+ If type==0x0010
|
||||
RECT
|
||||
+ If type==0x0011 (vector)
|
||||
DWORD Number of elements
|
||||
WORD Element's type
|
||||
BYTE[] As many values as the number of elements indicates
|
||||
Structure depends on the element's type
|
||||
+ If type==0x0012 (nested properties map)
|
||||
DWORD Number of properties
|
||||
BYTE[] Nested properties data
|
||||
Structure is the same as indicated in this loop
|
||||
|
||||
### Slice Chunk (0x2022)
|
||||
|
||||
|
@ -31,6 +31,8 @@
|
||||
#include "zlib.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <deque>
|
||||
#include <variant>
|
||||
|
||||
namespace app {
|
||||
|
||||
@ -143,12 +145,16 @@ static void ase_file_write_header_filesize(FILE* f, dio::AsepriteHeader* header)
|
||||
static void ase_file_prepare_frame_header(FILE* f, dio::AsepriteFrameHeader* frame_header);
|
||||
static void ase_file_write_frame_header(FILE* f, dio::AsepriteFrameHeader* frame_header);
|
||||
|
||||
static void ase_file_write_layers(FILE* f, dio::AsepriteFrameHeader* frame_header, const Layer* layer, int child_level);
|
||||
static layer_t ase_file_write_cels(FILE* f, dio::AsepriteFrameHeader* frame_header,
|
||||
static void ase_file_write_layers(FILE* f, FileOp* fop,
|
||||
dio::AsepriteFrameHeader* frame_header,
|
||||
const dio::AsepriteExternalFiles& ext_files,
|
||||
const Layer* layer, int child_level);
|
||||
static layer_t ase_file_write_cels(FILE* f, FileOp* fop,
|
||||
dio::AsepriteFrameHeader* frame_header,
|
||||
const dio::AsepriteExternalFiles& ext_files,
|
||||
const Sprite* sprite, const Layer* layer,
|
||||
layer_t layer_index,
|
||||
const frame_t frame,
|
||||
const frame_t firstFrame);
|
||||
const frame_t frame);
|
||||
|
||||
static void ase_file_write_padding(FILE* f, int bytes);
|
||||
static void ase_file_write_string(FILE* f, const std::string& string);
|
||||
@ -175,12 +181,18 @@ static void ase_file_write_mask_chunk(FILE* f, dio::AsepriteFrameHeader* frame_h
|
||||
#endif
|
||||
static void ase_file_write_tags_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, const Tags* tags,
|
||||
const frame_t fromFrame, const frame_t toFrame);
|
||||
static void ase_file_write_slice_chunks(FILE* f, dio::AsepriteFrameHeader* frame_header, const Slices& slices,
|
||||
static void ase_file_write_slice_chunks(FILE* f, FileOp* fop,
|
||||
dio::AsepriteFrameHeader* frame_header,
|
||||
const dio::AsepriteExternalFiles& ext_files,
|
||||
const Slices& slices,
|
||||
const frame_t fromFrame, const frame_t toFrame);
|
||||
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_external_files_chunk(FILE* f,
|
||||
static void ase_file_write_user_data_chunk(FILE* f, FileOp* fop,
|
||||
dio::AsepriteFrameHeader* frame_header,
|
||||
const dio::AsepriteExternalFiles& ext_files,
|
||||
const UserData* userData);
|
||||
static void ase_file_write_external_files_chunk(FILE* f, FileOp* fop,
|
||||
dio::AsepriteFrameHeader* frame_header,
|
||||
dio::AsepriteExternalFiles& ext_files,
|
||||
const Sprite* sprite);
|
||||
@ -193,6 +205,11 @@ static void ase_file_write_tileset_chunk(FILE* f, FileOp* fop,
|
||||
const dio::AsepriteExternalFiles& ext_files,
|
||||
const Tileset* tileset,
|
||||
const tileset_index si);
|
||||
static void ase_file_write_properties(FILE* f,
|
||||
const UserData::Properties& properties);
|
||||
static void ase_file_write_properties_maps(FILE* f, FileOp* fop,
|
||||
const dio::AsepriteExternalFiles& ext_files,
|
||||
const doc::UserData::PropertiesMaps& propertiesMaps);
|
||||
static bool ase_has_groups(LayerGroup* group);
|
||||
static void ase_ungroup_all(LayerGroup* group);
|
||||
|
||||
@ -351,7 +368,7 @@ bool AseFormat::onSave(FileOp* fop)
|
||||
|
||||
if (outputFrame == 0) {
|
||||
// Check if we need the "external files" chunk
|
||||
ase_file_write_external_files_chunk(f, &frame_header, ext_files, sprite);
|
||||
ase_file_write_external_files_chunk(f, fop, &frame_header, ext_files, sprite);
|
||||
|
||||
// Save color profile in first frame
|
||||
if (fop->preserveColorProfile())
|
||||
@ -379,7 +396,7 @@ bool AseFormat::onSave(FileOp* fop)
|
||||
if (frame == fop->roi().fromFrame()) {
|
||||
// Write sprite user data only if needed
|
||||
if (!sprite->userData().isEmpty())
|
||||
ase_file_write_user_data_chunk(f, &frame_header, &sprite->userData());
|
||||
ase_file_write_user_data_chunk(f, fop, &frame_header, ext_files, &sprite->userData());
|
||||
|
||||
// Write tilesets
|
||||
ase_file_write_tileset_chunks(f, fop, &frame_header, ext_files,
|
||||
@ -392,7 +409,7 @@ bool AseFormat::onSave(FileOp* fop)
|
||||
fop->roi().toFrame());
|
||||
// Write user data for tags
|
||||
for (doc::Tag* tag : sprite->tags()) {
|
||||
ase_file_write_user_data_chunk(f, &frame_header, &(tag->userData()));
|
||||
ase_file_write_user_data_chunk(f, fop, &frame_header, ext_files, &(tag->userData()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -401,19 +418,20 @@ bool AseFormat::onSave(FileOp* fop)
|
||||
// before layers so older version don't get confused by the new
|
||||
// user data chunks for tags.
|
||||
for (Layer* child : sprite->root()->layers())
|
||||
ase_file_write_layers(f, &frame_header, child, 0);
|
||||
ase_file_write_layers(f, fop, &frame_header, ext_files, child, 0);
|
||||
|
||||
// Write slice chunks
|
||||
ase_file_write_slice_chunks(f, &frame_header,
|
||||
ase_file_write_slice_chunks(f, fop, &frame_header,
|
||||
ext_files,
|
||||
sprite->slices(),
|
||||
fop->roi().fromFrame(),
|
||||
fop->roi().toFrame());
|
||||
}
|
||||
|
||||
// Write cel chunks
|
||||
ase_file_write_cels(f, &frame_header,
|
||||
ase_file_write_cels(f, fop, &frame_header, ext_files,
|
||||
sprite, sprite->root(),
|
||||
0, frame, fop->roi().fromFrame());
|
||||
0, frame);
|
||||
|
||||
// Write the frame header
|
||||
ase_file_write_frame_header(f, &frame_header);
|
||||
@ -541,37 +559,41 @@ static void ase_file_write_frame_header(FILE* f, dio::AsepriteFrameHeader* frame
|
||||
fseek(f, end, SEEK_SET);
|
||||
}
|
||||
|
||||
static void ase_file_write_layers(FILE* f, dio::AsepriteFrameHeader* frame_header, const Layer* layer, int child_index)
|
||||
static void ase_file_write_layers(FILE* f, FileOp* fop,
|
||||
dio::AsepriteFrameHeader* frame_header,
|
||||
const dio::AsepriteExternalFiles& ext_files,
|
||||
const Layer* layer, int child_index)
|
||||
{
|
||||
ase_file_write_layer_chunk(f, frame_header, layer, child_index);
|
||||
if (!layer->userData().isEmpty())
|
||||
ase_file_write_user_data_chunk(f, frame_header, &layer->userData());
|
||||
ase_file_write_user_data_chunk(f, fop, frame_header, ext_files, &layer->userData());
|
||||
|
||||
if (layer->isGroup()) {
|
||||
for (const Layer* child : static_cast<const LayerGroup*>(layer)->layers())
|
||||
ase_file_write_layers(f, frame_header, child, child_index+1);
|
||||
ase_file_write_layers(f, fop, frame_header, ext_files, child, child_index+1);
|
||||
}
|
||||
}
|
||||
|
||||
static layer_t ase_file_write_cels(FILE* f, dio::AsepriteFrameHeader* frame_header,
|
||||
static layer_t ase_file_write_cels(FILE* f, FileOp* fop,
|
||||
dio::AsepriteFrameHeader* frame_header,
|
||||
const dio::AsepriteExternalFiles& ext_files,
|
||||
const Sprite* sprite, const Layer* layer,
|
||||
layer_t layer_index,
|
||||
const frame_t frame,
|
||||
const frame_t firstFrame)
|
||||
const frame_t frame)
|
||||
{
|
||||
if (layer->isImage()) {
|
||||
const Cel* cel = layer->cel(frame);
|
||||
if (cel) {
|
||||
ase_file_write_cel_chunk(f, frame_header, cel,
|
||||
static_cast<const LayerImage*>(layer),
|
||||
layer_index, sprite, firstFrame);
|
||||
layer_index, sprite, fop->roi().fromFrame());
|
||||
|
||||
if (layer->isReference())
|
||||
ase_file_write_cel_extra_chunk(f, frame_header, cel);
|
||||
|
||||
if (!cel->link() &&
|
||||
!cel->data()->userData().isEmpty()) {
|
||||
ase_file_write_user_data_chunk(f, frame_header,
|
||||
ase_file_write_user_data_chunk(f, fop, frame_header, ext_files,
|
||||
&cel->data()->userData());
|
||||
}
|
||||
}
|
||||
@ -583,8 +605,8 @@ static layer_t ase_file_write_cels(FILE* f, dio::AsepriteFrameHeader* frame_head
|
||||
if (layer->isGroup()) {
|
||||
for (const Layer* child : static_cast<const LayerGroup*>(layer)->layers()) {
|
||||
layer_index =
|
||||
ase_file_write_cels(f, frame_header, sprite, child,
|
||||
layer_index, frame, firstFrame);
|
||||
ase_file_write_cels(f, fop, frame_header, ext_files, sprite, child,
|
||||
layer_index, frame);
|
||||
}
|
||||
}
|
||||
|
||||
@ -605,6 +627,18 @@ static void ase_file_write_string(FILE* f, const std::string& string)
|
||||
fputc(string[c], f);
|
||||
}
|
||||
|
||||
static void ase_file_write_point(FILE* f, const gfx::Point& point)
|
||||
{
|
||||
fputl(point.x, f);
|
||||
fputl(point.y, f);
|
||||
}
|
||||
|
||||
static void ase_file_write_size(FILE* f, const gfx::Size& size)
|
||||
{
|
||||
fputl(size.w, f);
|
||||
fputl(size.h, f);
|
||||
}
|
||||
|
||||
static void ase_file_write_start_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, int type, dio::AsepriteChunk* chunk)
|
||||
{
|
||||
frame_header->chunks++;
|
||||
@ -1122,7 +1156,10 @@ static void ase_file_write_tags_chunk(FILE* f,
|
||||
}
|
||||
}
|
||||
|
||||
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, FileOp* fop,
|
||||
dio::AsepriteFrameHeader* frame_header,
|
||||
const dio::AsepriteExternalFiles& ext_files,
|
||||
const UserData* userData)
|
||||
{
|
||||
ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_USER_DATA);
|
||||
|
||||
@ -1131,6 +1168,8 @@ static void ase_file_write_user_data_chunk(FILE* f, dio::AsepriteFrameHeader* fr
|
||||
flags |= ASE_USER_DATA_FLAG_HAS_TEXT;
|
||||
if (doc::rgba_geta(userData->color()))
|
||||
flags |= ASE_USER_DATA_FLAG_HAS_COLOR;
|
||||
if (!userData->propertiesMaps().empty())
|
||||
flags |= ASE_USER_DATA_FLAG_HAS_PROPERTIES;
|
||||
fputl(flags, f);
|
||||
|
||||
if (flags & ASE_USER_DATA_FLAG_HAS_TEXT)
|
||||
@ -1142,9 +1181,15 @@ static void ase_file_write_user_data_chunk(FILE* f, dio::AsepriteFrameHeader* fr
|
||||
fputc(doc::rgba_getb(userData->color()), f);
|
||||
fputc(doc::rgba_geta(userData->color()), f);
|
||||
}
|
||||
|
||||
if (flags & ASE_USER_DATA_FLAG_HAS_PROPERTIES) {
|
||||
ase_file_write_properties_maps(f, fop, ext_files, userData->propertiesMaps());
|
||||
}
|
||||
}
|
||||
|
||||
static void ase_file_write_slice_chunks(FILE* f, dio::AsepriteFrameHeader* frame_header,
|
||||
static void ase_file_write_slice_chunks(FILE* f, FileOp* fop,
|
||||
dio::AsepriteFrameHeader* frame_header,
|
||||
const dio::AsepriteExternalFiles& ext_files,
|
||||
const Slices& slices,
|
||||
const frame_t fromFrame,
|
||||
const frame_t toFrame)
|
||||
@ -1158,7 +1203,7 @@ static void ase_file_write_slice_chunks(FILE* f, dio::AsepriteFrameHeader* frame
|
||||
fromFrame, toFrame);
|
||||
|
||||
if (!slice->userData().isEmpty())
|
||||
ase_file_write_user_data_chunk(f, frame_header, &slice->userData());
|
||||
ase_file_write_user_data_chunk(f, fop, frame_header, ext_files, &slice->userData());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1228,29 +1273,77 @@ static void ase_file_write_slice_chunk(FILE* f, dio::AsepriteFrameHeader* frame_
|
||||
}
|
||||
|
||||
static void ase_file_write_external_files_chunk(
|
||||
FILE* f,
|
||||
FILE* f, FileOp* fop,
|
||||
dio::AsepriteFrameHeader* frame_header,
|
||||
dio::AsepriteExternalFiles& ext_files,
|
||||
const Sprite* sprite)
|
||||
{
|
||||
auto putExtentionIds = [](const UserData::PropertiesMaps& propertiesMaps, dio::AsepriteExternalFiles& ext_files) {
|
||||
for (auto propertiesMap : propertiesMaps) {
|
||||
if (!propertiesMap.first.empty())
|
||||
ext_files.put(propertiesMap.first, ASE_EXTERNAL_FILE_EXTENSION);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
ext_files.put(tileset->externalFilename(), ASE_EXTERNAL_FILE_TILESET);
|
||||
}
|
||||
|
||||
putExtentionIds(tileset->userData().propertiesMaps(), ext_files);
|
||||
|
||||
for (tile_index i=0; i < tileset->size(); ++i) {
|
||||
UserData tileData = tileset->getTileData(i);
|
||||
putExtentionIds(tileData.propertiesMaps(), ext_files);
|
||||
}
|
||||
}
|
||||
|
||||
putExtentionIds(sprite->userData().propertiesMaps(), ext_files);
|
||||
|
||||
for (doc::Tag* tag : sprite->tags()) {
|
||||
putExtentionIds(tag->userData().propertiesMaps(), ext_files);
|
||||
}
|
||||
|
||||
// Go through all the layers collecting all the extension IDs we find
|
||||
std::deque<Layer*> layers(sprite->root()->layers().begin(), sprite->root()->layers().end());
|
||||
while (!layers.empty()) {
|
||||
auto layer = layers.front();
|
||||
layers.pop_front();
|
||||
|
||||
putExtentionIds(layer->userData().propertiesMaps(), ext_files);
|
||||
if (layer->isGroup()) {
|
||||
auto childLayers = static_cast<const LayerGroup*>(layer)->layers();
|
||||
layers.insert(layers.end(), childLayers.begin(), childLayers.end());
|
||||
}
|
||||
else if (layer->isImage()) {
|
||||
for (frame_t frame : fop->roi().selectedFrames()) {
|
||||
const Cel* cel = layer->cel(frame);
|
||||
if (cel && !cel->link()) {
|
||||
putExtentionIds(cel->data()->userData().propertiesMaps(), ext_files);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Slice* slice : sprite->slices()) {
|
||||
// Skip slices that are outside of the given ROI
|
||||
if (slice->range(fop->roi().fromFrame(), fop->roi().toFrame()).empty())
|
||||
continue;
|
||||
|
||||
putExtentionIds(slice->userData().propertiesMaps(), ext_files);
|
||||
}
|
||||
|
||||
// No external files to write
|
||||
if (ext_files.lastid == 0)
|
||||
return;
|
||||
|
||||
ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_EXTERNAL_FILE);
|
||||
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);
|
||||
fputc(ext_files.types[item.first], f); // Type
|
||||
ase_file_write_padding(f, 7);
|
||||
ase_file_write_string(f, item.second); // Filename
|
||||
}
|
||||
}
|
||||
@ -1264,6 +1357,14 @@ static void ase_file_write_tileset_chunks(FILE* f, FileOp* fop,
|
||||
for (const Tileset* tileset : *tilesets) {
|
||||
ase_file_write_tileset_chunk(f, fop, frame_header, ext_files,
|
||||
tileset, si);
|
||||
|
||||
ase_file_write_user_data_chunk(f, fop, frame_header, ext_files, &tileset->userData());
|
||||
|
||||
// Write tile UserData
|
||||
for (tile_index i=0; i < tileset->size(); ++i) {
|
||||
UserData tileData = tileset->getTileData(i);
|
||||
ase_file_write_user_data_chunk(f, fop, frame_header, ext_files, &tileData);
|
||||
}
|
||||
++si;
|
||||
}
|
||||
}
|
||||
@ -1325,6 +1426,133 @@ static void ase_file_write_tileset_chunk(FILE* f, FileOp* fop,
|
||||
}
|
||||
}
|
||||
|
||||
static void ase_file_write_property_value(FILE* f,
|
||||
const UserData::Variant& value)
|
||||
{
|
||||
if (const bool* v = std::get_if<bool>(&value)) {
|
||||
fputc(*v, f);
|
||||
}
|
||||
else if (const int8_t* v = std::get_if<int8_t>(&value)) {
|
||||
fputc(*v, f);
|
||||
}
|
||||
else if (const uint8_t* v = std::get_if<uint8_t>(&value)) {
|
||||
fputc(*v, f);
|
||||
}
|
||||
else if (const int16_t* v = std::get_if<int16_t>(&value)) {
|
||||
fputw(*v, f);
|
||||
}
|
||||
else if (const uint16_t* v = std::get_if<uint16_t>(&value)) {
|
||||
fputw(*v, f);
|
||||
}
|
||||
else if (const int32_t* v = std::get_if<int32_t>(&value)) {
|
||||
fputl(*v, f);
|
||||
}
|
||||
else if (const uint32_t* v = std::get_if<uint32_t>(&value)) {
|
||||
fputl(*v, f);
|
||||
}
|
||||
else if (const int64_t* v = std::get_if<int64_t>(&value)) {
|
||||
fputq(*v, f);
|
||||
}
|
||||
else if (const uint64_t* v = std::get_if<uint64_t>(&value)) {
|
||||
fputq(*v, f);
|
||||
}
|
||||
else if (const UserData::Fixed* v = std::get_if<UserData::Fixed>(&value)) {
|
||||
fputl(v->value, f);
|
||||
}
|
||||
else if (const float_t* v = std::get_if<float_t>(&value)) {
|
||||
fputf(*v, f);
|
||||
}
|
||||
else if (const double_t* v = std::get_if<double_t>(&value)) {
|
||||
fputd(*v, f);
|
||||
}
|
||||
else if (const std::string* v = std::get_if<std::string>(&value)) {
|
||||
ase_file_write_string(f, *v);
|
||||
}
|
||||
else if (const gfx::Point* v = std::get_if<gfx::Point>(&value)) {
|
||||
ase_file_write_point(f, *v);
|
||||
}
|
||||
else if (const gfx::Size* v = std::get_if<gfx::Size>(&value)) {
|
||||
ase_file_write_size(f, *v);
|
||||
}
|
||||
else if (const gfx::Rect* v = std::get_if<gfx::Rect>(&value)) {
|
||||
ase_file_write_point(f, v->origin());
|
||||
ase_file_write_size(f, v->size());
|
||||
}
|
||||
else if (const std::vector<UserData::Variant>* v = std::get_if<std::vector<UserData::Variant>>(&value)) {
|
||||
fputl(v->size(), f);
|
||||
const uint16_t type = v->size() == 0 ? 0 : v->front().type();
|
||||
fputw(type, f);
|
||||
for (auto elem : *v) {
|
||||
ase_file_write_property_value(f, elem);
|
||||
}
|
||||
}
|
||||
else if (const UserData::Properties* v = std::get_if<UserData::Properties>(&value)) {
|
||||
ase_file_write_properties(f, *v);
|
||||
}
|
||||
}
|
||||
|
||||
static void ase_file_write_properties(FILE* f,
|
||||
const UserData::Properties& properties)
|
||||
{
|
||||
ASSERT(properties.size() > 0);
|
||||
|
||||
fputl(properties.size(), f);
|
||||
|
||||
for (auto property : properties) {
|
||||
const std::string& name = property.first;
|
||||
ase_file_write_string(f, name);
|
||||
|
||||
const UserData::Variant& value = property.second;
|
||||
fputw(value.type(), f);
|
||||
|
||||
ase_file_write_property_value(f, value);
|
||||
}
|
||||
}
|
||||
|
||||
static void ase_file_write_properties_maps(FILE* f, FileOp* fop,
|
||||
const dio::AsepriteExternalFiles& ext_files,
|
||||
const doc::UserData::PropertiesMaps& propertiesMaps)
|
||||
{
|
||||
uint32_t numMaps = propertiesMaps.size();
|
||||
if (numMaps == 0) return;
|
||||
|
||||
long startPos = ftell(f);
|
||||
// We zero the size in bytes of all properties maps stored in this
|
||||
// chunk for now. (actual value is calculated after serialization
|
||||
// of all properties maps, at which point this field is overwritten)
|
||||
fputl(0, f);
|
||||
|
||||
fputl(numMaps, f);
|
||||
for (auto propertiesMap : propertiesMaps) {
|
||||
const UserData::Properties& properties = propertiesMap.second;
|
||||
// Skip properties map if it doesn't have any property
|
||||
if (properties.size() == 0) continue;
|
||||
|
||||
const std::string& extensionKey = propertiesMap.first;
|
||||
try {
|
||||
uint32_t extensionId = extensionKey == "" ? 0 : ext_files.to_id.at(extensionKey);
|
||||
fputl(extensionId, f);
|
||||
}
|
||||
catch(const std::out_of_range&) {
|
||||
ASSERT(false); // This shouldn't ever happen, but if it does...
|
||||
// most likely it is because we forgot to add the
|
||||
// extensionID to the ext_files object. And this
|
||||
// Could happen if someone adds the possibility to
|
||||
// store custom properties to some object that
|
||||
// didn't support it previously.
|
||||
fop->setError("Error writing properties for extension '%s'.\n", extensionKey.c_str());
|
||||
continue;
|
||||
}
|
||||
ase_file_write_properties(f, properties);
|
||||
}
|
||||
long endPos = ftell(f);
|
||||
// We can overwrite the properties maps size now
|
||||
fseek(f, startPos, SEEK_SET);
|
||||
fputl(endPos-startPos, f);
|
||||
// Let's go back to where we were
|
||||
fseek(f, endPos, SEEK_SET);
|
||||
}
|
||||
|
||||
static bool ase_has_groups(LayerGroup* group)
|
||||
{
|
||||
for (Layer* child : group->layers()) {
|
||||
|
@ -12,11 +12,15 @@
|
||||
#include "app/doc.h"
|
||||
#include "app/file/file.h"
|
||||
#include "app/file/file_formats_manager.h"
|
||||
#include "base/base64.h"
|
||||
#include "doc/doc.h"
|
||||
#include "doc/user_data.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
|
||||
using namespace app;
|
||||
|
||||
@ -78,3 +82,209 @@ TEST(File, SeveralSizes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(File, CustomProperties)
|
||||
{
|
||||
app::Context ctx;
|
||||
|
||||
struct TestCase {
|
||||
std::string filename;
|
||||
int w;
|
||||
int h;
|
||||
doc::ColorMode mode;
|
||||
int ncolors;
|
||||
doc::UserData::PropertiesMaps propertiesMaps;
|
||||
std::function<void(const TestCase&, doc::Sprite*)> setProperties;
|
||||
std::function<void(const TestCase&, doc::Sprite*)> assertions;
|
||||
};
|
||||
std::vector<TestCase> tests = {
|
||||
{ // Test sprite's userData simple custom properties
|
||||
"test_props_1.ase", 50, 50, doc::ColorMode::INDEXED, 256,
|
||||
{
|
||||
{"", {
|
||||
{"number", int32_t(560304)},
|
||||
{"is_solid", bool(true)},
|
||||
{"label", std::string("Rock")},
|
||||
{"weight", doc::UserData::Fixed{fixmath::ftofix(50.34)}},
|
||||
{"big_number", int64_t(9223372036854775807)},
|
||||
{"unsigned_big_number", uint64_t(18446744073709551615ULL)},
|
||||
}
|
||||
}
|
||||
},
|
||||
[](const TestCase& test, doc::Sprite* sprite){
|
||||
sprite->userData().propertiesMaps() = test.propertiesMaps;
|
||||
},
|
||||
[](const TestCase& test, doc::Sprite* sprite){
|
||||
ASSERT_EQ(doc::get_value<int32_t>(sprite->userData().properties()["number"]), 560304);
|
||||
ASSERT_EQ(doc::get_value<bool>(sprite->userData().properties()["is_solid"]), true);
|
||||
ASSERT_EQ(doc::get_value<std::string>(sprite->userData().properties()["label"]), "Rock");
|
||||
ASSERT_EQ(doc::get_value<doc::UserData::Fixed>(sprite->userData().properties()["weight"]).value, fixmath::ftofix(50.34));
|
||||
ASSERT_EQ(doc::get_value<int64_t>(sprite->userData().properties()["big_number"]), 9223372036854775807);
|
||||
ASSERT_EQ(doc::get_value<uint64_t>(sprite->userData().properties()["unsigned_big_number"]), 18446744073709551615ULL);
|
||||
}
|
||||
},
|
||||
{ // Test sprite's userData extension's simple properties
|
||||
"test_props_2.ase", 50, 50, doc::ColorMode::INDEXED, 256,
|
||||
{
|
||||
{"extensionIdentification", {
|
||||
{"number", int32_t(160304)},
|
||||
{"is_solid", bool(false)},
|
||||
{"label", std::string("Smoke")},
|
||||
{"weight", doc::UserData::Fixed{fixmath::ftofix(0.14)}}
|
||||
}
|
||||
}
|
||||
},
|
||||
[](const TestCase& test, doc::Sprite* sprite){
|
||||
sprite->userData().propertiesMaps() = test.propertiesMaps;
|
||||
},
|
||||
[](const TestCase& test, doc::Sprite* sprite){
|
||||
ASSERT_EQ(doc::get_value<int32_t>(sprite->userData().properties("extensionIdentification")["number"]), 160304);
|
||||
ASSERT_EQ(doc::get_value<bool>(sprite->userData().properties("extensionIdentification")["is_solid"]), false);
|
||||
ASSERT_EQ(doc::get_value<std::string>(sprite->userData().properties("extensionIdentification")["label"]), "Smoke");
|
||||
ASSERT_EQ(doc::get_value<doc::UserData::Fixed>(sprite->userData().properties("extensionIdentification")["weight"]).value, fixmath::ftofix(0.14));
|
||||
}
|
||||
},
|
||||
{ // Test sprite's userData custom + extension's simple properties
|
||||
"test_props_3.ase", 50, 50, doc::ColorMode::INDEXED, 256,
|
||||
{
|
||||
{"", {
|
||||
{"number", int32_t(560304)},
|
||||
{"is_solid", bool(true)},
|
||||
{"label", std::string("Rock")},
|
||||
{"weight", doc::UserData::Fixed{fixmath::ftofix(50.34)}}
|
||||
}
|
||||
},
|
||||
{"extensionIdentification", {
|
||||
{"number", int32_t(160304)},
|
||||
{"is_solid", bool(false)},
|
||||
{"label", std::string("Smoke")},
|
||||
{"weight", doc::UserData::Fixed{fixmath::ftofix(0.14)}}
|
||||
}
|
||||
}
|
||||
},
|
||||
[](const TestCase& test, doc::Sprite* sprite){
|
||||
sprite->userData().propertiesMaps() = test.propertiesMaps;
|
||||
},
|
||||
[](const TestCase& test, doc::Sprite* sprite){
|
||||
ASSERT_EQ(doc::get_value<int32_t>(sprite->userData().properties()["number"]), 560304);
|
||||
ASSERT_EQ(doc::get_value<bool>(sprite->userData().properties()["is_solid"]), true);
|
||||
ASSERT_EQ(doc::get_value<std::string>(sprite->userData().properties()["label"]), "Rock");
|
||||
ASSERT_EQ(doc::get_value<doc::UserData::Fixed>(sprite->userData().properties()["weight"]).value, fixmath::ftofix(50.34));
|
||||
|
||||
ASSERT_EQ(doc::get_value<int32_t>(sprite->userData().properties("extensionIdentification")["number"]), 160304);
|
||||
ASSERT_EQ(doc::get_value<bool>(sprite->userData().properties("extensionIdentification")["is_solid"]), false);
|
||||
ASSERT_EQ(doc::get_value<std::string>(sprite->userData().properties("extensionIdentification")["label"]), "Smoke");
|
||||
ASSERT_EQ(doc::get_value<doc::UserData::Fixed>(sprite->userData().properties("extensionIdentification")["weight"]).value, fixmath::ftofix(0.14));
|
||||
}
|
||||
},
|
||||
{ // Test sprite's userData complex properties
|
||||
"test_props_4.ase", 50, 50, doc::ColorMode::INDEXED, 256,
|
||||
{
|
||||
{"", {
|
||||
{"coordinates", gfx::Point(10, 20)},
|
||||
{"size", gfx::Size(100, 200)},
|
||||
{"bounds", gfx::Rect(30, 40, 150, 250)},
|
||||
{"items", doc::UserData::Vector {
|
||||
std::string("arrow"), std::string("hammer"), std::string("coin")
|
||||
}},
|
||||
{"player", doc::UserData::Properties {
|
||||
{"lives", uint8_t(5)},
|
||||
{"name", std::string("John Doe")},
|
||||
{"energy", uint16_t(1000)}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{"ext", {
|
||||
{"numbers", doc::UserData::Vector {int32_t(11), int32_t(22), int32_t(33)}},
|
||||
{"player", doc::UserData::Properties {
|
||||
{"id", uint32_t(12347455)},
|
||||
{"coordinates", gfx::Point(45, 56)},
|
||||
{"cards", doc::UserData::Vector {int8_t(11), int8_t(6), int8_t(0), int8_t(13)}}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[](const TestCase& test, doc::Sprite* sprite){
|
||||
sprite->userData().propertiesMaps() = test.propertiesMaps;
|
||||
},
|
||||
[](const TestCase& test, doc::Sprite* sprite){
|
||||
ASSERT_EQ(doc::get_value<gfx::Point>(sprite->userData().properties()["coordinates"]),
|
||||
gfx::Point(10, 20));
|
||||
ASSERT_EQ(doc::get_value<gfx::Size>(sprite->userData().properties()["size"]),
|
||||
gfx::Size(100, 200));
|
||||
ASSERT_EQ(doc::get_value<gfx::Rect>(sprite->userData().properties()["bounds"]),
|
||||
gfx::Rect(30, 40, 150, 250));
|
||||
ASSERT_EQ(doc::get_value<doc::UserData::Vector>(sprite->userData().properties()["items"]),
|
||||
(doc::UserData::Vector{std::string("arrow"), std::string("hammer"), std::string("coin")}));
|
||||
ASSERT_EQ(doc::get_value<doc::UserData::Properties>(sprite->userData().properties()["player"]),
|
||||
(doc::UserData::Properties {
|
||||
{"lives", uint8_t(5)},
|
||||
{"name", std::string("John Doe")},
|
||||
{"energy", uint16_t(1000)}
|
||||
}));
|
||||
|
||||
ASSERT_EQ(doc::get_value<doc::UserData::Vector>(sprite->userData().properties("ext")["numbers"]),
|
||||
(doc::UserData::Vector {int32_t(11), int32_t(22), int32_t(33)}));
|
||||
|
||||
ASSERT_EQ(doc::get_value<doc::UserData::Properties>(sprite->userData().properties("ext")["player"]),
|
||||
(doc::UserData::Properties {
|
||||
{"id", uint32_t(12347455)},
|
||||
{"coordinates", gfx::Point(45, 56)},
|
||||
{"cards", doc::UserData::Vector {int8_t(11), int8_t(6), int8_t(0), int8_t(13)}}
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (const TestCase& test : tests) {
|
||||
{
|
||||
std::unique_ptr<Doc> doc(
|
||||
ctx.documents().add(test.w, test.h, test.mode, test.ncolors));
|
||||
doc->setFilename(test.filename);
|
||||
// Random pixels
|
||||
LayerImage* layer = static_cast<LayerImage*>(doc->sprite()->root()->firstLayer());
|
||||
ASSERT_TRUE(layer != NULL);
|
||||
ImageRef image = layer->cel(frame_t(0))->imageRef();
|
||||
|
||||
std::srand(test.w*test.h);
|
||||
int c = 0;
|
||||
for (int y=0; y<test.h; y++) {
|
||||
for (int x=0; x<test.w; x++) {
|
||||
if ((std::rand()&4) == 0)
|
||||
c = std::rand()%test.ncolors;
|
||||
put_pixel_fast<IndexedTraits>(image.get(), x, y, c);
|
||||
}
|
||||
}
|
||||
|
||||
test.setProperties(test, doc->sprite());
|
||||
|
||||
save_document(&ctx, doc.get());
|
||||
doc->close();
|
||||
}
|
||||
{
|
||||
std::unique_ptr<Doc> doc(load_document(&ctx, test.filename));
|
||||
ASSERT_EQ(test.w, doc->sprite()->width());
|
||||
ASSERT_EQ(test.h, doc->sprite()->height());
|
||||
|
||||
// Same random pixels (see the seed)
|
||||
Layer* layer = doc->sprite()->root()->firstLayer();
|
||||
ASSERT_TRUE(layer != nullptr);
|
||||
Image* image = layer->cel(frame_t(0))->image();
|
||||
std::srand(test.w*test.h);
|
||||
int c = 0;
|
||||
for (int y=0; y<test.h; y++) {
|
||||
for (int x=0; x<test.w; x++) {
|
||||
if ((std::rand()&4) == 0)
|
||||
c = std::rand()%test.ncolors;
|
||||
ASSERT_EQ(c, get_pixel_fast<IndexedTraits>(image, x, y));
|
||||
}
|
||||
}
|
||||
|
||||
test.assertions(test, doc->sprite());
|
||||
|
||||
doc->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,6 +61,19 @@ int get_value_from_lua(lua_State* L, int index) {
|
||||
return lua_tointeger(L, index);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// float
|
||||
|
||||
template<>
|
||||
void push_value_to_lua(lua_State* L, const float& value) {
|
||||
lua_pushnumber(L, value);
|
||||
}
|
||||
|
||||
template<>
|
||||
float get_value_from_lua(lua_State* L, int index) {
|
||||
return lua_tonumber(L, index);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// double
|
||||
|
||||
@ -322,6 +335,12 @@ void push_value_to_lua(lua_State* L, const doc::UserData::Variant& value)
|
||||
case USER_DATA_PROPERTY_TYPE_FIXED:
|
||||
push_value_to_lua(L, *std::get_if<doc::UserData::Fixed>(&value));
|
||||
break;
|
||||
case USER_DATA_PROPERTY_TYPE_FLOAT:
|
||||
push_value_to_lua(L, *std::get_if<float>(&value));
|
||||
break;
|
||||
case USER_DATA_PROPERTY_TYPE_DOUBLE:
|
||||
push_value_to_lua(L, *std::get_if<double>(&value));
|
||||
break;
|
||||
case USER_DATA_PROPERTY_TYPE_STRING:
|
||||
push_value_to_lua(L, *std::get_if<std::string>(&value));
|
||||
break;
|
||||
@ -366,9 +385,7 @@ doc::UserData::Variant get_value_from_lua(lua_State* L, int index)
|
||||
if (lua_isinteger(L, index))
|
||||
v = lua_tointeger(L, index);
|
||||
else {
|
||||
v = doc::UserData::Fixed{
|
||||
fixmath::ftofix(lua_tonumber(L, index))
|
||||
};
|
||||
v = lua_tonumber(L, index);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -52,6 +52,7 @@
|
||||
|
||||
#define ASE_USER_DATA_FLAG_HAS_TEXT 1
|
||||
#define ASE_USER_DATA_FLAG_HAS_COLOR 2
|
||||
#define ASE_USER_DATA_FLAG_HAS_PROPERTIES 4
|
||||
|
||||
#define ASE_CEL_EXTRA_FLAG_PRECISE_BOUNDS 1
|
||||
|
||||
@ -62,6 +63,10 @@
|
||||
#define ASE_TILESET_FLAG_EMBEDDED 2
|
||||
#define ASE_TILESET_FLAG_ZERO_IS_NOTILE 4
|
||||
|
||||
#define ASE_EXTERNAL_FILE_PALETTE 0
|
||||
#define ASE_EXTERNAL_FILE_TILESET 1
|
||||
#define ASE_EXTERNAL_FILE_EXTENSION 2
|
||||
|
||||
namespace dio {
|
||||
|
||||
struct AsepriteHeader {
|
||||
@ -103,7 +108,22 @@ struct AsepriteChunk {
|
||||
struct AsepriteExternalFiles {
|
||||
std::map<uint32_t, std::string> to_fn; // ID -> filename
|
||||
std::map<std::string, uint32_t> to_id; // filename -> ID
|
||||
std::map<uint32_t, uint8_t> types; // ID -> type (type has one of the ASE_EXTERNAL_FILE_* values)
|
||||
|
||||
uint32_t lastid = 0;
|
||||
// Adds the external filename with the next autogenerated ID and specified type.
|
||||
void put(const std::string& filename, uint8_t type) {
|
||||
auto id = ++lastid;
|
||||
to_fn[id] = filename;
|
||||
to_id[filename] = id;
|
||||
types[id] = type;
|
||||
}
|
||||
// Adds the external filename using the specified ID and type.
|
||||
void put(uint32_t id, const std::string& filename, uint8_t type) {
|
||||
to_fn[id] = filename;
|
||||
to_id[filename] = id;
|
||||
types[id] = type;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace dio
|
||||
|
@ -20,7 +20,6 @@
|
||||
#include "dio/file_interface.h"
|
||||
#include "dio/pixel_io.h"
|
||||
#include "doc/doc.h"
|
||||
#include "doc/user_data.h"
|
||||
#include "doc/util.h"
|
||||
#include "fixmath/fixmath.h"
|
||||
#include "fmt/format.h"
|
||||
@ -59,12 +58,12 @@ bool AsepriteDecoder::decode()
|
||||
|
||||
// Create the new sprite
|
||||
std::unique_ptr<doc::Sprite> sprite(
|
||||
new doc::Sprite(doc::ImageSpec(header.depth == 32 ? doc::ColorMode::RGB:
|
||||
header.depth == 16 ? doc::ColorMode::GRAYSCALE:
|
||||
doc::ColorMode::INDEXED,
|
||||
header.width,
|
||||
header.height),
|
||||
header.ncolors));
|
||||
std::make_unique<doc::Sprite>(doc::ImageSpec(header.depth == 32 ? doc::ColorMode::RGB:
|
||||
header.depth == 16 ? doc::ColorMode::GRAYSCALE:
|
||||
doc::ColorMode::INDEXED,
|
||||
header.width,
|
||||
header.height),
|
||||
header.ncolors));
|
||||
|
||||
// Set frames and speed
|
||||
sprite->setTotalFrames(doc::frame_t(header.frames));
|
||||
@ -228,38 +227,51 @@ bool AsepriteDecoder::decode()
|
||||
|
||||
case ASE_FILE_CHUNK_USER_DATA: {
|
||||
doc::UserData userData;
|
||||
readUserDataChunk(&userData);
|
||||
readUserDataChunk(&userData, extFiles);
|
||||
|
||||
if (last_object_with_user_data) {
|
||||
last_object_with_user_data->setUserData(userData);
|
||||
|
||||
if (last_object_with_user_data->type() == doc::ObjectType::Tag) {
|
||||
// Tags are a special case, user data for tags come
|
||||
// all together (one next to other) after the tags
|
||||
// chunk, in the same order:
|
||||
//
|
||||
// * TAGS CHUNK (TAG1, TAG2, ..., TAGn)
|
||||
// * USER DATA CHUNK FOR TAG1
|
||||
// * USER DATA CHUNK FOR TAG2
|
||||
// * ...
|
||||
// * USER DATA CHUNK FOR TAGn
|
||||
//
|
||||
// So here we expect that the next user data chunk
|
||||
// will correspond to the next tag in the tags
|
||||
// collection.
|
||||
++tag_it;
|
||||
switch(last_object_with_user_data->type()) {
|
||||
case doc::ObjectType::Tag:
|
||||
// Tags are a special case, user data for tags come
|
||||
// all together (one next to other) after the tags
|
||||
// chunk, in the same order:
|
||||
//
|
||||
// * TAGS CHUNK (TAG1, TAG2, ..., TAGn)
|
||||
// * USER DATA CHUNK FOR TAG1
|
||||
// * USER DATA CHUNK FOR TAG2
|
||||
// * ...
|
||||
// * USER DATA CHUNK FOR TAGn
|
||||
//
|
||||
// So here we expect that the next user data chunk
|
||||
// will correspond to the next tag in the tags
|
||||
// collection.
|
||||
++tag_it;
|
||||
|
||||
if (tag_it != tag_end)
|
||||
last_object_with_user_data = *tag_it;
|
||||
else
|
||||
if (tag_it != tag_end)
|
||||
last_object_with_user_data = *tag_it;
|
||||
else
|
||||
last_object_with_user_data = nullptr;
|
||||
break;
|
||||
case doc::ObjectType::Tileset:
|
||||
// Read tiles user datas.
|
||||
// TODO: Should we refactor how tile user data is handled so we can actually
|
||||
// decode this user data chunks the same way as the user data chunks for
|
||||
// the tags?
|
||||
doc::Tileset* tileset = static_cast<doc::Tileset*>(last_object_with_user_data);
|
||||
readTilesData(tileset, extFiles);
|
||||
last_object_with_user_data = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ASE_FILE_CHUNK_TILESET: {
|
||||
readTilesetChunk(sprite.get(), &header, extFiles);
|
||||
doc::Tileset* tileset = readTilesetChunk(sprite.get(), &header, extFiles);
|
||||
if (tileset)
|
||||
last_object_with_user_data = tileset;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -378,6 +390,67 @@ std::string AsepriteDecoder::readString()
|
||||
return string;
|
||||
}
|
||||
|
||||
float AsepriteDecoder::readFloat()
|
||||
{
|
||||
int b1, b2, b3, b4;
|
||||
|
||||
if ((b1 = read8()) == EOF)
|
||||
return EOF;
|
||||
|
||||
if ((b2 = read8()) == EOF)
|
||||
return EOF;
|
||||
|
||||
if ((b3 = read8()) == EOF)
|
||||
return EOF;
|
||||
|
||||
if ((b4 = read8()) == EOF)
|
||||
return EOF;
|
||||
|
||||
// Little endian.
|
||||
int v = ((b4 << 24) | (b3 << 16) | (b2 << 8) | b1);
|
||||
return *reinterpret_cast<float*>(&v);
|
||||
}
|
||||
|
||||
double AsepriteDecoder::readDouble()
|
||||
{
|
||||
int b1, b2, b3, b4, b5, b6, b7, b8;
|
||||
|
||||
if ((b1 = read8()) == EOF)
|
||||
return EOF;
|
||||
|
||||
if ((b2 = read8()) == EOF)
|
||||
return EOF;
|
||||
|
||||
if ((b3 = read8()) == EOF)
|
||||
return EOF;
|
||||
|
||||
if ((b4 = read8()) == EOF)
|
||||
return EOF;
|
||||
|
||||
if ((b5 = read8()) == EOF)
|
||||
return EOF;
|
||||
|
||||
if ((b6 = read8()) == EOF)
|
||||
return EOF;
|
||||
|
||||
if ((b7 = read8()) == EOF)
|
||||
return EOF;
|
||||
|
||||
if ((b8 = read8()) == EOF)
|
||||
return EOF;
|
||||
|
||||
// Little endian.
|
||||
long long v = (((long long)b8 << 56) |
|
||||
((long long)b7 << 48) |
|
||||
((long long)b6 << 40) |
|
||||
((long long)b5 << 32) |
|
||||
((long long)b4 << 24) |
|
||||
((long long)b3 << 16) |
|
||||
((long long)b2 << 8) |
|
||||
(long long)b1);
|
||||
return *reinterpret_cast<double*>(&v);
|
||||
}
|
||||
|
||||
doc::Palette* AsepriteDecoder::readColorChunk(doc::Palette* prevPal,
|
||||
doc::frame_t frame)
|
||||
{
|
||||
@ -771,7 +844,7 @@ doc::Cel* AsepriteDecoder::readCelChunk(doc::Sprite* sprite,
|
||||
break;
|
||||
}
|
||||
|
||||
cel.reset(new doc::Cel(frame, image));
|
||||
cel = std::make_unique<doc::Cel>(frame, image);
|
||||
cel->setPosition(x, y);
|
||||
cel->setOpacity(opacity);
|
||||
}
|
||||
@ -812,7 +885,7 @@ doc::Cel* AsepriteDecoder::readCelChunk(doc::Sprite* sprite,
|
||||
doc::ImageRef image(doc::Image::create(pixelFormat, w, h));
|
||||
read_compressed_image(f(), delegate(), image.get(), header, chunk_end);
|
||||
|
||||
cel.reset(new doc::Cel(frame, image));
|
||||
cel = std::make_unique<doc::Cel>(frame, image);
|
||||
cel->setPosition(x, y);
|
||||
cel->setOpacity(opacity);
|
||||
}
|
||||
@ -857,7 +930,7 @@ doc::Cel* AsepriteDecoder::readCelChunk(doc::Sprite* sprite,
|
||||
return doc::tile_geti(tile) >= tilesetSize ? doc::notile : tile;
|
||||
});
|
||||
|
||||
cel.reset(new doc::Cel(frame, image));
|
||||
cel = std::make_unique<doc::Cel>(frame, image);
|
||||
cel->setPosition(x, y);
|
||||
cel->setOpacity(opacity);
|
||||
}
|
||||
@ -938,10 +1011,10 @@ void AsepriteDecoder::readExternalFiles(AsepriteExternalFiles& extFiles)
|
||||
readPadding(8);
|
||||
for (uint32_t i=0; i<n; ++i) {
|
||||
uint32_t id = read32();
|
||||
readPadding(8);
|
||||
uint8_t type = read8();
|
||||
readPadding(7);
|
||||
std::string fn = readString();
|
||||
extFiles.to_fn[id] = fn;
|
||||
extFiles.to_id[fn] = id;
|
||||
extFiles.put(id, fn, type);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1016,7 +1089,8 @@ void AsepriteDecoder::readTagsChunk(doc::Tags* tags)
|
||||
}
|
||||
}
|
||||
|
||||
void AsepriteDecoder::readUserDataChunk(doc::UserData* userData)
|
||||
void AsepriteDecoder::readUserDataChunk(doc::UserData* userData,
|
||||
const AsepriteExternalFiles& extFiles)
|
||||
{
|
||||
size_t flags = read32();
|
||||
|
||||
@ -1032,6 +1106,10 @@ void AsepriteDecoder::readUserDataChunk(doc::UserData* userData)
|
||||
int a = read8();
|
||||
userData->setColor(doc::rgba(r, g, b, a));
|
||||
}
|
||||
|
||||
if (flags & ASE_USER_DATA_FLAG_HAS_PROPERTIES) {
|
||||
readPropertiesMaps(userData->propertiesMaps(), extFiles);
|
||||
}
|
||||
}
|
||||
|
||||
void AsepriteDecoder::readSlicesChunk(doc::Slices& slices)
|
||||
@ -1057,7 +1135,7 @@ doc::Slice* AsepriteDecoder::readSliceChunk(doc::Slices& slices)
|
||||
read32(); // 4 bytes reserved
|
||||
std::string name = readString(); // Name
|
||||
|
||||
std::unique_ptr<doc::Slice> slice(new doc::Slice);
|
||||
std::unique_ptr<doc::Slice> slice(std::make_unique<doc::Slice>());
|
||||
slice->setName(name);
|
||||
|
||||
// For each key
|
||||
@ -1089,7 +1167,7 @@ doc::Slice* AsepriteDecoder::readSliceChunk(doc::Slices& slices)
|
||||
return slice.release();
|
||||
}
|
||||
|
||||
void AsepriteDecoder::readTilesetChunk(doc::Sprite* sprite,
|
||||
doc::Tileset* AsepriteDecoder::readTilesetChunk(doc::Sprite* sprite,
|
||||
const AsepriteHeader* header,
|
||||
const AsepriteExternalFiles& extFiles)
|
||||
{
|
||||
@ -1107,7 +1185,7 @@ void AsepriteDecoder::readTilesetChunk(doc::Sprite* sprite,
|
||||
delegate()->error(
|
||||
fmt::format("Error: Invalid tileset (number of tiles={0}, tile size={1}x{2})",
|
||||
ntiles, w, h));
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
doc::Grid grid(gfx::Size(w, h));
|
||||
@ -1158,6 +1236,154 @@ void AsepriteDecoder::readTilesetChunk(doc::Sprite* sprite,
|
||||
if (id >= m_tilesetFlags.size())
|
||||
m_tilesetFlags.resize(id+1, 0);
|
||||
m_tilesetFlags[id] = flags;
|
||||
|
||||
return tileset;
|
||||
}
|
||||
|
||||
void AsepriteDecoder::readPropertiesMaps(doc::UserData::PropertiesMaps& propertiesMaps,
|
||||
const AsepriteExternalFiles& extFiles)
|
||||
{
|
||||
auto startPos = f()->tell();
|
||||
auto size = read32();
|
||||
auto numMaps = read32();
|
||||
for (int i=0; i<numMaps; ++i) {
|
||||
auto id = read32();
|
||||
std::string extensionId = "";
|
||||
if (id) {
|
||||
try {
|
||||
extensionId = extFiles.to_fn.at(id);
|
||||
}
|
||||
catch (const std::out_of_range&) {
|
||||
// This shouldn't happen, but if it does, we put the properties
|
||||
// in an artificial extensionId.
|
||||
extensionId = fmt::format("__missed__{}", id);
|
||||
delegate()->error(
|
||||
fmt::format("Error: Invalid extension ID (id={0} not found)", id));
|
||||
}
|
||||
}
|
||||
auto properties = readPropertyValue(USER_DATA_PROPERTY_TYPE_PROPERTIES);
|
||||
propertiesMaps[extensionId] = *std::get_if<doc::UserData::Properties>(&properties);
|
||||
}
|
||||
f()->seek(startPos+size);
|
||||
}
|
||||
|
||||
const doc::UserData::Variant AsepriteDecoder::readPropertyValue(uint16_t type)
|
||||
{
|
||||
switch (type) {
|
||||
case USER_DATA_PROPERTY_TYPE_BOOL: {
|
||||
bool value = read8();
|
||||
return value;
|
||||
}
|
||||
case USER_DATA_PROPERTY_TYPE_INT8: {
|
||||
int8_t value = read8();
|
||||
return value;
|
||||
}
|
||||
case USER_DATA_PROPERTY_TYPE_UINT8: {
|
||||
uint8_t value = read8();
|
||||
return value;
|
||||
}
|
||||
case USER_DATA_PROPERTY_TYPE_INT16: {
|
||||
int16_t value = read16();
|
||||
return value;
|
||||
}
|
||||
case USER_DATA_PROPERTY_TYPE_UINT16: {
|
||||
uint16_t value = read16();
|
||||
return value;
|
||||
}
|
||||
case USER_DATA_PROPERTY_TYPE_INT32: {
|
||||
int32_t value = read32();
|
||||
return value;
|
||||
}
|
||||
case USER_DATA_PROPERTY_TYPE_UINT32: {
|
||||
uint32_t value = read32();
|
||||
return value;
|
||||
}
|
||||
case USER_DATA_PROPERTY_TYPE_INT64: {
|
||||
int64_t value = read64();
|
||||
return value;
|
||||
}
|
||||
case USER_DATA_PROPERTY_TYPE_UINT64: {
|
||||
uint64_t value = read64();
|
||||
return value;
|
||||
}
|
||||
case USER_DATA_PROPERTY_TYPE_FIXED: {
|
||||
int32_t value = read32();
|
||||
return doc::UserData::Fixed{value};
|
||||
}
|
||||
case USER_DATA_PROPERTY_TYPE_FLOAT: {
|
||||
float value = readFloat();
|
||||
return value;
|
||||
}
|
||||
case USER_DATA_PROPERTY_TYPE_DOUBLE: {
|
||||
double value = readDouble();
|
||||
return value;
|
||||
}
|
||||
case USER_DATA_PROPERTY_TYPE_STRING: {
|
||||
std::string value = readString();
|
||||
return value;
|
||||
}
|
||||
case USER_DATA_PROPERTY_TYPE_POINT: {
|
||||
int32_t x = read32();
|
||||
int32_t y = read32();
|
||||
return gfx::Point(x, y);
|
||||
}
|
||||
case USER_DATA_PROPERTY_TYPE_SIZE: {
|
||||
int32_t w = read32();
|
||||
int32_t h = read32();
|
||||
return gfx::Size(w, h);
|
||||
}
|
||||
case USER_DATA_PROPERTY_TYPE_RECT: {
|
||||
int32_t x = read32();
|
||||
int32_t y = read32();
|
||||
int32_t w = read32();
|
||||
int32_t h = read32();
|
||||
return gfx::Rect(x, y, w, h);
|
||||
}
|
||||
case USER_DATA_PROPERTY_TYPE_VECTOR: {
|
||||
auto numElems = read32();
|
||||
auto elemsType = read16();
|
||||
std::vector<doc::UserData::Variant> value;
|
||||
for (int k=0; k<numElems;++k) {
|
||||
value.push_back(readPropertyValue(elemsType));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
case USER_DATA_PROPERTY_TYPE_PROPERTIES: {
|
||||
auto numProps = read32();
|
||||
doc::UserData::Properties value;
|
||||
for (int j=0; j<numProps;++j) {
|
||||
auto name = readString();
|
||||
auto type = read16();
|
||||
value[name] = readPropertyValue(type);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return doc::UserData::Variant{};
|
||||
}
|
||||
|
||||
void AsepriteDecoder::readTilesData(doc::Tileset* tileset, const AsepriteExternalFiles& extFiles)
|
||||
{
|
||||
// Read as many user data chunks as tiles are in the tileset
|
||||
for (doc::tile_index i=0; i < tileset->size(); i++) {
|
||||
size_t chunk_pos = f()->tell();
|
||||
// Read chunk information
|
||||
int chunk_size = read32();
|
||||
int chunk_type = read16();
|
||||
if (chunk_type != ASE_FILE_CHUNK_USER_DATA) {
|
||||
// Something went wrong...
|
||||
delegate()->error(
|
||||
fmt::format("WARNING: Unexpected chunk type {0} when reading tileset index {1}", chunk_type, i));
|
||||
f()->seek(chunk_pos);
|
||||
return;
|
||||
}
|
||||
|
||||
doc::UserData tileData;
|
||||
readUserDataChunk(&tileData, extFiles);
|
||||
tileset->setTileData(i, tileData);
|
||||
f()->seek(chunk_pos+chunk_size);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dio
|
||||
|
@ -15,6 +15,8 @@
|
||||
#include "doc/pixel_format.h"
|
||||
#include "doc/slices.h"
|
||||
#include "doc/tags.h"
|
||||
#include "doc/tileset.h"
|
||||
#include "doc/user_data.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@ -44,6 +46,8 @@ private:
|
||||
void readFrameHeader(AsepriteFrameHeader* frame_header);
|
||||
void readPadding(const int bytes);
|
||||
std::string readString();
|
||||
float readFloat();
|
||||
double readDouble();
|
||||
doc::Palette* readColorChunk(doc::Palette* prevPal, doc::frame_t frame);
|
||||
doc::Palette* readColor2Chunk(doc::Palette* prevPal, doc::frame_t frame);
|
||||
doc::Palette* readPaletteChunk(doc::Palette* prevPal, doc::frame_t frame);
|
||||
@ -60,10 +64,15 @@ private:
|
||||
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 AsepriteExternalFiles& extFiles);
|
||||
void readUserDataChunk(doc::UserData* userData,
|
||||
const AsepriteExternalFiles& extFiles);
|
||||
doc::Tileset* readTilesetChunk(doc::Sprite* sprite,
|
||||
const AsepriteHeader* header,
|
||||
const AsepriteExternalFiles& extFiles);
|
||||
void readPropertiesMaps(doc::UserData::PropertiesMaps& propertiesMaps,
|
||||
const AsepriteExternalFiles& extFiles);
|
||||
const doc::UserData::Variant readPropertyValue(uint16_t type);
|
||||
void readTilesData(doc::Tileset* tileset, const AsepriteExternalFiles& extFiles);
|
||||
|
||||
doc::LayerList m_allLayers;
|
||||
std::vector<uint32_t> m_tilesetFlags;
|
||||
|
@ -60,6 +60,32 @@ uint32_t Decoder::read32()
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t Decoder::read64()
|
||||
{
|
||||
int b1 = m_f->read8();
|
||||
int b2 = m_f->read8();
|
||||
int b3 = m_f->read8();
|
||||
int b4 = m_f->read8();
|
||||
int b5 = m_f->read8();
|
||||
int b6 = m_f->read8();
|
||||
int b7 = m_f->read8();
|
||||
int b8 = m_f->read8();
|
||||
|
||||
if (m_f->ok()) {
|
||||
// Little endian
|
||||
return (((long long)b8 << 56) |
|
||||
((long long)b7 << 48) |
|
||||
((long long)b6 << 40) |
|
||||
((long long)b5 << 32) |
|
||||
((long long)b4 << 24) |
|
||||
((long long)b3 << 16) |
|
||||
((long long)b2 << 8) |
|
||||
(long long)b1);
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t Decoder::readBytes(uint8_t* buf, size_t n)
|
||||
{
|
||||
return m_f->readBytes(buf, n);
|
||||
|
@ -35,6 +35,7 @@ protected:
|
||||
uint8_t read8();
|
||||
uint16_t read16();
|
||||
uint32_t read32();
|
||||
uint64_t read64();
|
||||
size_t readBytes(uint8_t* buf, size_t n);
|
||||
|
||||
private:
|
||||
|
@ -31,12 +31,14 @@
|
||||
#define USER_DATA_PROPERTY_TYPE_INT64 0x0008
|
||||
#define USER_DATA_PROPERTY_TYPE_UINT64 0x0009
|
||||
#define USER_DATA_PROPERTY_TYPE_FIXED 0x000A
|
||||
#define USER_DATA_PROPERTY_TYPE_STRING 0x000B
|
||||
#define USER_DATA_PROPERTY_TYPE_POINT 0x000C
|
||||
#define USER_DATA_PROPERTY_TYPE_SIZE 0x000D
|
||||
#define USER_DATA_PROPERTY_TYPE_RECT 0x000E
|
||||
#define USER_DATA_PROPERTY_TYPE_VECTOR 0x000F
|
||||
#define USER_DATA_PROPERTY_TYPE_PROPERTIES 0x0010
|
||||
#define USER_DATA_PROPERTY_TYPE_FLOAT 0x000B
|
||||
#define USER_DATA_PROPERTY_TYPE_DOUBLE 0x000C
|
||||
#define USER_DATA_PROPERTY_TYPE_STRING 0x000D
|
||||
#define USER_DATA_PROPERTY_TYPE_POINT 0x000E
|
||||
#define USER_DATA_PROPERTY_TYPE_SIZE 0x000F
|
||||
#define USER_DATA_PROPERTY_TYPE_RECT 0x0010
|
||||
#define USER_DATA_PROPERTY_TYPE_VECTOR 0x0011
|
||||
#define USER_DATA_PROPERTY_TYPE_PROPERTIES 0x0012
|
||||
|
||||
namespace doc {
|
||||
|
||||
@ -44,6 +46,10 @@ namespace doc {
|
||||
public:
|
||||
struct Fixed {
|
||||
fixmath::fixed value;
|
||||
|
||||
bool operator==(const Fixed& f) const {
|
||||
return this->value == f.value;
|
||||
}
|
||||
};
|
||||
struct Variant;
|
||||
using Vector = std::vector<Variant>;
|
||||
@ -55,6 +61,7 @@ namespace doc {
|
||||
int32_t, uint32_t,
|
||||
int64_t, uint64_t,
|
||||
Fixed,
|
||||
float, double,
|
||||
std::string,
|
||||
gfx::Point,
|
||||
gfx::Size,
|
||||
|
Loading…
x
Reference in New Issue
Block a user