Merge branch 'custom-properties-serialization' (fix #3632, #3645)

This commit is contained in:
David Capello 2023-01-06 10:03:19 -03:00
commit 8f09728105
10 changed files with 908 additions and 89 deletions

View File

@ -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)

View File

@ -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()) {

View File

@ -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();
}
}
}

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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:

View File

@ -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,