Move .aseprite file encoder to dio module (related to #379)

In this way we'll be able to use the encoder in a future
module (e.g. dll COM server) to generate thumbnails.
This commit is contained in:
David Capello 2018-01-02 13:06:19 -03:00
parent 11817e27fe
commit cf07af155f
18 changed files with 1599 additions and 981 deletions

View File

@ -46,11 +46,11 @@ because they don't depend on any other component.
## Level 4
* [dio](dio/) (base, flic): Load/save documents.
* [dio](dio/) (base, doc, fixmath, flic): Load/save sprites/documents.
## Level 5
* [app](app/) (allegro, base, doc, dio, filters, fixmath, flic, gfx, pen, render, scripting, she, ui, undo, updater, webserver)
* [app](app/) (base, doc, dio, filters, fixmath, flic, gfx, pen, render, scripting, she, ui, undo, updater, webserver)
## Level 6

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,17 @@
# Aseprite Document IO Library
# Copyright (c) 2016-2017 David Capello
# Copyright (c) 2016-2018 David Capello
add_library(dio-lib
detect_format.cpp)
aseprite_decoder.cpp
decode_file.cpp
decoder.cpp
detect_format.cpp
stdio.cpp)
target_link_libraries(dio-lib
${ZLIB_LIBRARIES}
fmt
flic-lib
laf-base)
laf-base
fixmath-lib
doc-lib)

View File

@ -1,4 +1,4 @@
Copyright (c) 2016-2017 David Capello
Copyright (c) 2016-2018 David Capello
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View File

@ -1,4 +1,4 @@
# Aseprite Document IO Library
*Copyright (C) 2016-2017 David Capello*
*Copyright (C) 2016-2018 David Capello*
> Distributed under [MIT license](LICENSE.txt)

82
src/dio/aseprite_common.h Normal file
View File

@ -0,0 +1,82 @@
// Aseprite Document IO Library
// Copyright (c) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DIO_ASEPRITE_COMMON_H_INCLUDED
#define DIO_ASEPRITE_COMMON_H_INCLUDED
#pragma once
#define ASE_FILE_MAGIC 0xA5E0
#define ASE_FILE_FRAME_MAGIC 0xF1FA
#define ASE_FILE_FLAG_LAYER_WITH_OPACITY 1
#define ASE_FILE_CHUNK_FLI_COLOR2 4
#define ASE_FILE_CHUNK_FLI_COLOR 11
#define ASE_FILE_CHUNK_LAYER 0x2004
#define ASE_FILE_CHUNK_CEL 0x2005
#define ASE_FILE_CHUNK_CEL_EXTRA 0x2006
#define ASE_FILE_CHUNK_MASK 0x2016
#define ASE_FILE_CHUNK_PATH 0x2017
#define ASE_FILE_CHUNK_FRAME_TAGS 0x2018
#define ASE_FILE_CHUNK_PALETTE 0x2019
#define ASE_FILE_CHUNK_USER_DATA 0x2020
#define ASE_FILE_CHUNK_SLICES 0x2021 // Deprecated chunk (used on dev versions only between v1.2-beta7 and v1.2-beta8)
#define ASE_FILE_CHUNK_SLICE 0x2022
#define ASE_FILE_LAYER_IMAGE 0
#define ASE_FILE_LAYER_GROUP 1
#define ASE_FILE_RAW_CEL 0
#define ASE_FILE_LINK_CEL 1
#define ASE_FILE_COMPRESSED_CEL 2
#define ASE_PALETTE_FLAG_HAS_NAME 1
#define ASE_USER_DATA_FLAG_HAS_TEXT 1
#define ASE_USER_DATA_FLAG_HAS_COLOR 2
#define ASE_CEL_EXTRA_FLAG_PRECISE_BOUNDS 1
#define ASE_SLICE_FLAG_HAS_CENTER_BOUNDS 1
#define ASE_SLICE_FLAG_HAS_PIVOT_POINT 2
namespace dio {
struct AsepriteHeader {
long pos; // TODO used by the encoder in app project
uint32_t size;
uint16_t magic;
uint16_t frames;
uint16_t width;
uint16_t height;
uint16_t depth;
uint32_t flags;
uint16_t speed; // Deprecated, use "duration" of AsepriteFrameHeader
uint32_t next;
uint32_t frit;
uint8_t transparent_index;
uint8_t ignore[3];
uint16_t ncolors;
uint8_t pixel_width;
uint8_t pixel_height;
};
struct AsepriteFrameHeader {
uint32_t size;
uint16_t magic;
uint16_t chunks;
uint16_t duration;
};
struct AsepriteChunk {
int type;
int start;
};
} // namespace dio
#endif

View File

@ -0,0 +1,864 @@
// Aseprite Document IO Library
// Copyright (c) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "dio/aseprite_decoder.h"
#include "base/cfile.h"
#include "base/exception.h"
#include "base/file_handle.h"
#include "base/fs.h"
#include "dio/aseprite_common.h"
#include "dio/decode_delegate.h"
#include "dio/file_interface.h"
#include "dio/pixel_io.h"
#include "doc/doc.h"
#include "fixmath/fixmath.h"
#include "fmt/format.h"
#include "ui/alert.h"
#include "zlib.h"
#include <cstdio>
namespace dio {
bool AsepriteDecoder::decode()
{
bool ignore_old_color_chunks = false;
AsepriteHeader header;
if (!readHeader(&header)) {
delegate()->error("Error reading header");
return false;
}
// Create the new sprite
base::UniquePtr<doc::Sprite> sprite(
new doc::Sprite(header.depth == 32 ? doc::IMAGE_RGB:
header.depth == 16 ? doc::IMAGE_GRAYSCALE:
doc::IMAGE_INDEXED,
header.width,
header.height,
header.ncolors));
// Set frames and speed
sprite->setTotalFrames(doc::frame_t(header.frames));
sprite->setDurationForAllFrames(header.speed);
// Set transparent entry
sprite->setTransparentColor(header.transparent_index);
// Set pixel ratio
sprite->setPixelRatio(doc::PixelRatio(header.pixel_width, header.pixel_height));
// Prepare variables for layer chunks
doc::Layer* last_layer = sprite->root();
doc::WithUserData* last_object_with_user_data = nullptr;
doc::Cel* last_cel = nullptr;
int current_level = -1;
doc::LayerList allLayers;
// Just one frame?
doc::frame_t nframes = sprite->totalFrames();
if (nframes > 1 && delegate()->decodeOneFrame())
nframes = 1;
// Read frame by frame to end-of-file
for (doc::frame_t frame=0; frame<nframes; ++frame) {
// Start frame position
size_t frame_pos = f()->tell();
delegate()->progress((float)frame_pos / (float)header.size);
// Read frame header
AsepriteFrameHeader frame_header;
readFrameHeader(&frame_header);
// Correct frame type
if (frame_header.magic == ASE_FILE_FRAME_MAGIC) {
// Use frame-duration field?
if (frame_header.duration > 0)
sprite->setFrameDuration(frame, frame_header.duration);
// Read chunks
for (int c=0; c<frame_header.chunks; c++) {
// Start chunk position
size_t chunk_pos = f()->tell();
delegate()->progress((float)chunk_pos / (float)header.size);
// Read chunk information
int chunk_size = read32();
int chunk_type = read16();
switch (chunk_type) {
case ASE_FILE_CHUNK_FLI_COLOR:
case ASE_FILE_CHUNK_FLI_COLOR2:
if (!ignore_old_color_chunks) {
doc::Palette* prevPal = sprite->palette(frame);
base::UniquePtr<doc::Palette> pal(
chunk_type == ASE_FILE_CHUNK_FLI_COLOR ?
readColorChunk(prevPal, frame):
readColor2Chunk(prevPal, frame));
if (prevPal->countDiff(pal.get(), NULL, NULL) > 0)
sprite->setPalette(pal.get(), true);
}
break;
case ASE_FILE_CHUNK_PALETTE: {
doc::Palette* prevPal = sprite->palette(frame);
base::UniquePtr<doc::Palette> pal(
readPaletteChunk(prevPal, frame));
if (prevPal->countDiff(pal.get(), NULL, NULL) > 0)
sprite->setPalette(pal.get(), true);
ignore_old_color_chunks = true;
break;
}
case ASE_FILE_CHUNK_LAYER: {
doc::Layer* newLayer =
readLayerChunk(&header, sprite,
&last_layer,
&current_level);
if (newLayer) {
allLayers.push_back(newLayer);
last_object_with_user_data = newLayer;
}
break;
}
case ASE_FILE_CHUNK_CEL: {
doc::Cel* cel =
readCelChunk(sprite, allLayers, frame,
sprite->pixelFormat(), &header,
chunk_pos+chunk_size);
if (cel) {
last_cel = cel;
last_object_with_user_data = cel->data();
}
break;
}
case ASE_FILE_CHUNK_CEL_EXTRA: {
if (last_cel)
readCelExtraChunk(last_cel);
break;
}
case ASE_FILE_CHUNK_MASK: {
doc::Mask* mask = readMaskChunk();
if (mask)
delete mask; // TODO add the mask in some place?
else
delegate()->error("Warning: Cannot load a mask chunk");
break;
}
case ASE_FILE_CHUNK_PATH:
// Ignore
break;
case ASE_FILE_CHUNK_FRAME_TAGS:
readFrameTagsChunk(&sprite->frameTags());
break;
case ASE_FILE_CHUNK_SLICES: {
readSlicesChunk(sprite->slices());
break;
}
case ASE_FILE_CHUNK_SLICE: {
doc::Slice* slice = readSliceChunk(sprite->slices());
if (slice)
last_object_with_user_data = slice;
break;
}
case ASE_FILE_CHUNK_USER_DATA: {
doc::UserData userData;
readUserDataChunk(&userData);
if (last_object_with_user_data)
last_object_with_user_data->setUserData(userData);
break;
}
default:
delegate()->error(
fmt::format("Warning: Unsupported chunk type {0} (skipping)", chunk_type));
break;
}
// Skip chunk size
f()->seek(chunk_pos+chunk_size);
}
}
// Skip frame size
f()->seek(frame_pos+frame_header.size);
if (delegate()->isCanceled())
break;
}
delegate()->onSprite(sprite.release());
return true;
}
bool AsepriteDecoder::readHeader(AsepriteHeader* header)
{
size_t headerPos = f()->tell();
header->size = read32();
header->magic = read16();
// Developers can open any .ase file
#if !defined(ENABLE_DEVMODE)
if (header->magic != ASE_FILE_MAGIC)
return false;
#endif
header->frames = read16();
header->width = read16();
header->height = read16();
header->depth = read16();
header->flags = read32();
header->speed = read16();
header->next = read32();
header->frit = read32();
header->transparent_index = read8();
header->ignore[0] = read8();
header->ignore[1] = read8();
header->ignore[2] = read8();
header->ncolors = read16();
header->pixel_width = read8();
header->pixel_height = read8();
if (header->ncolors == 0) // 0 means 256 (old .ase files)
header->ncolors = 256;
if (header->pixel_width == 0 ||
header->pixel_height == 0) {
header->pixel_width = 1;
header->pixel_height = 1;
}
#if defined(ENABLE_DEVMODE)
// This is useful to read broken .ase files
if (header->magic != ASE_FILE_MAGIC) {
header->frames = 256; // Frames number might be not enought for some files
header->width = 1024; // Size doesn't matter, the sprite can be crop
header->height = 1024;
}
#endif
f()->seek(headerPos+128);
return true;
}
void AsepriteDecoder::readFrameHeader(AsepriteFrameHeader* frame_header)
{
frame_header->size = read32();
frame_header->magic = read16();
frame_header->chunks = read16();
frame_header->duration = read16();
readPadding(6);
}
void AsepriteDecoder::readPadding(int bytes)
{
for (int c=0; c<bytes; c++)
read8();
}
std::string AsepriteDecoder::readString()
{
int length = read16();
if (length == EOF)
return "";
std::string string;
string.reserve(length+1);
for (int c=0; c<length; c++)
string.push_back(read8());
return string;
}
doc::Palette* AsepriteDecoder::readColorChunk(doc::Palette* prevPal,
doc::frame_t frame)
{
int i, c, r, g, b, packets, skip, size;
doc::Palette* pal = new doc::Palette(*prevPal);
pal->setFrame(frame);
packets = read16(); // Number of packets
skip = 0;
// Read all packets
for (i=0; i<packets; i++) {
skip += read8();
size = read8();
if (!size) size = 256;
for (c=skip; c<skip+size; c++) {
r = read8();
g = read8();
b = read8();
pal->setEntry(c, doc::rgba(doc::scale_6bits_to_8bits(r),
doc::scale_6bits_to_8bits(g),
doc::scale_6bits_to_8bits(b), 255));
}
}
return pal;
}
doc::Palette* AsepriteDecoder::readColor2Chunk(doc::Palette* prevPal,
doc::frame_t frame)
{
int i, c, r, g, b, packets, skip, size;
doc::Palette* pal = new doc::Palette(*prevPal);
pal->setFrame(frame);
packets = read16(); // Number of packets
skip = 0;
// Read all packets
for (i=0; i<packets; i++) {
skip += read8();
size = read8();
if (!size) size = 256;
for (c=skip; c<skip+size; c++) {
r = read8();
g = read8();
b = read8();
pal->setEntry(c, doc::rgba(r, g, b, 255));
}
}
return pal;
}
doc::Palette* AsepriteDecoder::readPaletteChunk(doc::Palette* prevPal,
doc::frame_t frame)
{
doc::Palette* pal = new doc::Palette(*prevPal);
pal->setFrame(frame);
int newSize = read32();
int from = read32();
int to = read32();
readPadding(8);
if (newSize > 0)
pal->resize(newSize);
for (int c=from; c<=to; ++c) {
int flags = read16();
int r = read8();
int g = read8();
int b = read8();
int a = read8();
pal->setEntry(c, doc::rgba(r, g, b, a));
// Skip name
if (flags & ASE_PALETTE_FLAG_HAS_NAME) {
std::string name = readString();
// Ignore color entry name
}
}
return pal;
}
doc::Layer* AsepriteDecoder::readLayerChunk(AsepriteHeader* header,
doc::Sprite* sprite,
doc::Layer** previous_layer,
int* current_level)
{
// Read chunk data
int flags = read16();
int layer_type = read16();
int child_level = read16();
read16(); // default width
read16(); // default height
int blendmode = read16(); // blend mode
int opacity = read8(); // opacity
readPadding(3);
std::string name = readString();
doc::Layer* layer = nullptr;
switch (layer_type) {
case ASE_FILE_LAYER_IMAGE:
layer = new doc::LayerImage(sprite);
// Only transparent layers can have blend mode and opacity
if (!(flags & int(doc::LayerFlags::Background))) {
static_cast<doc::LayerImage*>(layer)->setBlendMode((doc::BlendMode)blendmode);
if (header->flags & ASE_FILE_FLAG_LAYER_WITH_OPACITY)
static_cast<doc::LayerImage*>(layer)->setOpacity(opacity);
}
break;
case ASE_FILE_LAYER_GROUP:
layer = new doc::LayerGroup(sprite);
break;
}
if (layer) {
// flags
layer->setFlags(static_cast<doc::LayerFlags>(flags));
// name
layer->setName(name.c_str());
// Child level
if (child_level == *current_level)
(*previous_layer)->parent()->addLayer(layer);
else if (child_level > *current_level)
static_cast<doc::LayerGroup*>(*previous_layer)->addLayer(layer);
else if (child_level < *current_level) {
doc::LayerGroup* parent = (*previous_layer)->parent();
ASSERT(parent);
if (parent) {
int levels = (*current_level - child_level);
while (levels--) {
ASSERT(parent->parent());
if (!parent->parent())
break;
parent = parent->parent();
}
parent->addLayer(layer);
}
}
*previous_layer = layer;
*current_level = child_level;
}
return layer;
}
//////////////////////////////////////////////////////////////////////
// Raw Image
//////////////////////////////////////////////////////////////////////
template<typename ImageTraits>
void read_raw_image(FileInterface* f,
DecodeDelegate* delegate,
doc::Image* image,
AsepriteHeader* header)
{
PixelIO<ImageTraits> pixel_io;
int x, y;
int w = image->width();
int h = image->height();
for (y=0; y<h; ++y) {
for (x=0; x<w; ++x) {
doc::put_pixel_fast<ImageTraits>(image, x, y, pixel_io.read_pixel(f));
}
delegate->progress((float)f->tell() / (float)header->size);
}
}
//////////////////////////////////////////////////////////////////////
// Compressed Image
//////////////////////////////////////////////////////////////////////
template<typename ImageTraits>
void read_compressed_image(FileInterface* f,
DecodeDelegate* delegate,
doc::Image* image,
size_t chunk_end,
AsepriteHeader* header)
{
PixelIO<ImageTraits> pixel_io;
z_stream zstream;
int y, err;
zstream.zalloc = (alloc_func)0;
zstream.zfree = (free_func)0;
zstream.opaque = (voidpf)0;
err = inflateInit(&zstream);
if (err != Z_OK)
throw base::Exception("ZLib error %d in inflateInit().", err);
std::vector<uint8_t> scanline(ImageTraits::getRowStrideBytes(image->width()));
std::vector<uint8_t> uncompressed(image->height() * ImageTraits::getRowStrideBytes(image->width()));
std::vector<uint8_t> compressed(4096);
int uncompressed_offset = 0;
while (true) {
size_t input_bytes;
if (f->tell()+compressed.size() > chunk_end) {
input_bytes = chunk_end - f->tell(); // Remaining bytes
ASSERT(input_bytes < compressed.size());
if (input_bytes == 0)
break; // Done, we consumed all chunk
}
else
input_bytes = compressed.size();
size_t bytes_read = f->readBytes(&compressed[0], input_bytes);
zstream.next_in = (Bytef*)&compressed[0];
zstream.avail_in = bytes_read;
do {
zstream.next_out = (Bytef*)&scanline[0];
zstream.avail_out = scanline.size();
err = inflate(&zstream, Z_NO_FLUSH);
if (err != Z_OK && err != Z_STREAM_END && err != Z_BUF_ERROR)
throw base::Exception("ZLib error %d in inflate().", err);
size_t uncompressed_bytes = scanline.size() - zstream.avail_out;
if (uncompressed_bytes > 0) {
ASSERT(uncompressed_offset+uncompressed_bytes <= uncompressed.size());
if (uncompressed_offset+uncompressed_bytes > uncompressed.size())
throw base::Exception("Bad compressed image.");
std::copy(scanline.begin(), scanline.begin()+uncompressed_bytes,
uncompressed.begin()+uncompressed_offset);
uncompressed_offset += uncompressed_bytes;
}
} while (zstream.avail_out == 0);
delegate->progress((float)f->tell() / (float)header->size);
}
uncompressed_offset = 0;
for (y=0; y<image->height(); y++) {
typename ImageTraits::address_t address =
(typename ImageTraits::address_t)image->getPixelAddress(0, y);
pixel_io.read_scanline(address, image->width(), &uncompressed[uncompressed_offset]);
uncompressed_offset += ImageTraits::getRowStrideBytes(image->width());
}
err = inflateEnd(&zstream);
if (err != Z_OK)
throw base::Exception("ZLib error %d in inflateEnd().", err);
}
//////////////////////////////////////////////////////////////////////
// Cel Chunk
//////////////////////////////////////////////////////////////////////
doc::Cel* AsepriteDecoder::readCelChunk(doc::Sprite* sprite,
doc::LayerList& allLayers,
doc::frame_t frame,
doc::PixelFormat pixelFormat,
AsepriteHeader* header,
size_t chunk_end)
{
// Read chunk data
doc::layer_t layer_index = read16();
int x = ((short)read16());
int y = ((short)read16());
int opacity = read8();
int cel_type = read16();
readPadding(7);
doc::Layer* layer = nullptr;
if (layer_index >= 0 && layer_index < doc::layer_t(allLayers.size()))
layer = allLayers[layer_index];
if (!layer) {
delegate()->error(
fmt::format("Frame {0} didn't found layer with index {1}",
(int)frame, (int)layer_index));
return nullptr;
}
if (!layer->isImage()) {
delegate()->error(
fmt::format("Invalid .ase file (frame {0} in layer {1} which does not contain images",
(int)frame, (int)layer_index));
return nullptr;
}
// Create the new frame.
base::UniquePtr<doc::Cel> cel;
switch (cel_type) {
case ASE_FILE_RAW_CEL: {
// Read width and height
int w = read16();
int h = read16();
if (w > 0 && h > 0) {
doc::ImageRef image(doc::Image::create(pixelFormat, w, h));
// Read pixel data
switch (image->pixelFormat()) {
case doc::IMAGE_RGB:
read_raw_image<doc::RgbTraits>(f(), delegate(), image.get(), header);
break;
case doc::IMAGE_GRAYSCALE:
read_raw_image<doc::GrayscaleTraits>(f(), delegate(), image.get(), header);
break;
case doc::IMAGE_INDEXED:
read_raw_image<doc::IndexedTraits>(f(), delegate(), image.get(), header);
break;
}
cel.reset(new doc::Cel(frame, image));
cel->setPosition(x, y);
cel->setOpacity(opacity);
}
break;
}
case ASE_FILE_LINK_CEL: {
// Read link position
doc::frame_t link_frame = doc::frame_t(read16());
doc::Cel* link = layer->cel(link_frame);
if (link) {
// There were a beta version that allow to the user specify
// different X, Y, or opacity per link, in that case we must
// create a copy.
if (link->x() == x && link->y() == y && link->opacity() == opacity) {
cel.reset(doc::Cel::createLink(link));
cel->setFrame(frame);
}
else {
cel.reset(doc::Cel::createCopy(link));
cel->setFrame(frame);
cel->setPosition(x, y);
cel->setOpacity(opacity);
}
}
else {
// Linked cel doesn't found
return nullptr;
}
break;
}
case ASE_FILE_COMPRESSED_CEL: {
// Read width and height
int w = read16();
int h = read16();
if (w > 0 && h > 0) {
doc::ImageRef image(doc::Image::create(pixelFormat, w, h));
// Try to read pixel data
try {
switch (image->pixelFormat()) {
case doc::IMAGE_RGB:
read_compressed_image<doc::RgbTraits>(
f(), delegate(), image.get(), chunk_end, header);
break;
case doc::IMAGE_GRAYSCALE:
read_compressed_image<doc::GrayscaleTraits>(
f(), delegate(), image.get(), chunk_end, header);
break;
case doc::IMAGE_INDEXED:
read_compressed_image<doc::IndexedTraits>(
f(), delegate(), image.get(), chunk_end, header);
break;
}
}
// OK, in case of error we can show the problem, but continue
// loading more cels.
catch (const std::exception& e) {
delegate()->error(e.what());
}
cel.reset(new doc::Cel(frame, image));
cel->setPosition(x, y);
cel->setOpacity(opacity);
}
break;
}
}
if (!cel)
return nullptr;
static_cast<doc::LayerImage*>(layer)->addCel(cel);
return cel.release();
}
void AsepriteDecoder::readCelExtraChunk(doc::Cel* cel)
{
// Read chunk data
int flags = read32();
if (flags & ASE_CEL_EXTRA_FLAG_PRECISE_BOUNDS) {
fixmath::fixed x = read32();
fixmath::fixed y = read32();
fixmath::fixed w = read32();
fixmath::fixed h = read32();
if (w && h) {
gfx::RectF bounds(fixmath::fixtof(x),
fixmath::fixtof(y),
fixmath::fixtof(w),
fixmath::fixtof(h));
cel->setBoundsF(bounds);
}
}
}
doc::Mask* AsepriteDecoder::readMaskChunk()
{
int c, u, v, byte;
doc::Mask* mask;
// Read chunk data
int x = read16();
int y = read16();
int w = read16();
int h = read16();
readPadding(8);
std::string name = readString();
mask = new doc::Mask();
mask->setName(name.c_str());
mask->replace(gfx::Rect(x, y, w, h));
// Read image data
for (v=0; v<h; v++)
for (u=0; u<(w+7)/8; u++) {
byte = read8();
for (c=0; c<8; c++)
doc::put_pixel(mask->bitmap(), u*8+c, v, byte & (1<<(7-c)));
}
return mask;
}
void AsepriteDecoder::readFrameTagsChunk(doc::FrameTags* frameTags)
{
size_t tags = read16();
read32(); // 8 reserved bytes
read32();
for (size_t c=0; c<tags; ++c) {
doc::frame_t from = read16();
doc::frame_t to = read16();
int aniDir = read8();
if (aniDir != int(doc::AniDir::FORWARD) &&
aniDir != int(doc::AniDir::REVERSE) &&
aniDir != int(doc::AniDir::PING_PONG)) {
aniDir = int(doc::AniDir::FORWARD);
}
read32(); // 8 reserved bytes
read32();
int r = read8();
int g = read8();
int b = read8();
read8(); // Skip
std::string name = readString();
doc::FrameTag* tag = new doc::FrameTag(from, to);
tag->setColor(doc::rgba(r, g, b, 255));
tag->setName(name);
tag->setAniDir((doc::AniDir)aniDir);
frameTags->add(tag);
}
}
void AsepriteDecoder::readUserDataChunk(doc::UserData* userData)
{
size_t flags = read32();
if (flags & ASE_USER_DATA_FLAG_HAS_TEXT) {
std::string text = readString();
userData->setText(text);
}
if (flags & ASE_USER_DATA_FLAG_HAS_COLOR) {
int r = read8();
int g = read8();
int b = read8();
int a = read8();
userData->setColor(doc::rgba(r, g, b, a));
}
}
void AsepriteDecoder::readSlicesChunk(doc::Slices& slices)
{
size_t nslices = read32(); // Number of slices
read32(); // 8 bytes reserved
read32();
for (size_t i=0; i<nslices; ++i) {
doc::Slice* slice = readSliceChunk(slices);
// Set the user data
if (slice) {
// Default slice color
slice->userData().setColor(delegate()->defaultSliceColor());
}
}
}
doc::Slice* AsepriteDecoder::readSliceChunk(doc::Slices& slices)
{
const size_t nkeys = read32(); // Number of keys
const int flags = read32(); // Flags
read32(); // 4 bytes reserved
std::string name = readString(); // Name
base::UniquePtr<doc::Slice> slice(new doc::Slice);
slice->setName(name);
// For each key
for (size_t j=0; j<nkeys; ++j) {
gfx::Rect bounds, center;
gfx::Point pivot = doc::SliceKey::NoPivot;
doc::frame_t frame = read32();
bounds.x = read32();
bounds.y = read32();
bounds.w = read32();
bounds.h = read32();
if (flags & ASE_SLICE_FLAG_HAS_CENTER_BOUNDS) {
center.x = read32();
center.y = read32();
center.w = read32();
center.h = read32();
}
if (flags & ASE_SLICE_FLAG_HAS_PIVOT_POINT) {
pivot.x = read32();
pivot.y = read32();
}
slice->insert(frame, doc::SliceKey(bounds, center, pivot));
}
slices.add(slice);
return slice.release();
}
} // namespace dio

View File

@ -0,0 +1,64 @@
// Aseprite Document IO Library
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DIO_ASEPRITE_DECODER_H_INCLUDED
#define DIO_ASEPRITE_DECODER_H_INCLUDED
#pragma once
#include "dio/decoder.h"
#include "doc/frame.h"
#include "doc/frame_tags.h"
#include "doc/layer_list.h"
#include "doc/pixel_format.h"
#include "doc/slices.h"
#include <string>
namespace doc {
class Cel;
class Layer;
class Layer;
class Mask;
class Palette;
class Sprite;
class UserData;
}
namespace dio {
struct AsepriteHeader;
struct AsepriteFrameHeader;
class AsepriteDecoder : public Decoder {
public:
bool decode() override;
private:
bool readHeader(AsepriteHeader* header);
void readFrameHeader(AsepriteFrameHeader* frame_header);
void readPadding(const int bytes);
std::string readString();
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);
doc::Layer* readLayerChunk(AsepriteHeader* header, doc::Sprite* sprite, doc::Layer** previous_layer, int* current_level);
doc::Cel* readCelChunk(doc::Sprite* sprite,
doc::LayerList& allLayers,
doc::frame_t frame,
doc::PixelFormat pixelFormat,
AsepriteHeader* header,
size_t chunk_end);
void readCelExtraChunk(doc::Cel* cel);
doc::Mask* readMaskChunk();
void readFrameTagsChunk(doc::FrameTags* frameTags);
void readSlicesChunk(doc::Slices& slices);
doc::Slice* readSliceChunk(doc::Slices& slices);
void readUserDataChunk(doc::UserData* userData);
};
} // namespace dio
#endif

51
src/dio/decode_delegate.h Normal file
View File

@ -0,0 +1,51 @@
// Aseprite Document IO Library
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DIO_DECODE_DELEGATE_H_INCLUDED
#define DIO_DECODE_DELEGATE_H_INCLUDED
#pragma once
#include "doc/color.h"
#include "doc/frame.h"
#include "doc/sprite.h"
#include <string>
namespace dio {
class DecodeDelegate {
public:
virtual ~DecodeDelegate() { }
// Used to log errors
virtual void error(const std::string& msg) { }
// Used to report progress of the whole operation
virtual void progress(double fromZeroToOne) { }
// Return true if the operation was cancelled by the user
virtual bool isCanceled() { return false; }
// Return true if you want to read just the first frame (e.g. useful
// to generate a thumbnail)
virtual bool decodeOneFrame() { return false; }
// Default color for slices without user data
virtual doc::color_t defaultSliceColor() {
return doc::rgba(0, 0, 255, 255);
}
// Called when the sprite is decoded successfully
virtual void onSprite(doc::Sprite* sprite) {
// Discard the sprite, you should overwrite this behavior, use the
// sprite and then discard it when you don't need it anymore.
delete sprite;
}
};
} // namespace dio
#endif

47
src/dio/decode_file.cpp Normal file
View File

@ -0,0 +1,47 @@
// Aseprite Document IO Library
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "dio/decode_file.h"
#include "dio/aseprite_decoder.h"
#include "dio/decoder.h"
#include "dio/detect_format.h"
#include "dio/file_interface.h"
#include "doc/document.h"
#include <cassert>
namespace dio {
bool decode_file(DecodeDelegate* delegate,
FileInterface* f)
{
assert(delegate);
assert(f);
std::vector<uint8_t> buf(8, 0);
size_t n = f->readBytes(&buf[0], 8);
FileFormat format = detect_format_by_file_content_bytes(&buf[0], n);
f->seek(0); // Rewind
Decoder* decoder = nullptr;
switch (format) {
case FileFormat::ASE_ANIMATION: decoder = new AsepriteDecoder; break;
}
bool result = false;
if (decoder) {
decoder->initialize(delegate, f);
result = decoder->decode();
delete decoder;
}
return result;
}
} // namespace dio

21
src/dio/decode_file.h Normal file
View File

@ -0,0 +1,21 @@
// Aseprite Document IO Library
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DIO_DECODE_FILE_H_INCLUDED
#define DIO_DECODE_FILE_H_INCLUDED
#pragma once
namespace dio {
class DecodeDelegate;
class FileInterface;
bool decode_file(DecodeDelegate* delegate,
FileInterface* f);
} // namespace dio
#endif

62
src/dio/decoder.cpp Normal file
View File

@ -0,0 +1,62 @@
// Aseprite Document IO Library
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "dio/decoder.h"
#include "dio/file_interface.h"
#include "doc/document.h"
namespace dio {
Decoder::Decoder()
: m_delegate(nullptr)
, m_f(nullptr)
{
}
Decoder::~Decoder()
{
}
void Decoder::initialize(DecodeDelegate* delegate, FileInterface* f)
{
m_delegate = delegate;
m_f = f;
}
uint8_t Decoder::read8()
{
return m_f->read8();
}
uint16_t Decoder::read16()
{
int b1 = m_f->read8();
int b2 = m_f->read8();
if (m_f->ok()) {
return ((b2 << 8) | b1); // Little endian
}
else
return 0;
}
uint32_t Decoder::read32()
{
int b1 = m_f->read8();
int b2 = m_f->read8();
int b3 = m_f->read8();
int b4 = m_f->read8();
if (m_f->ok()) {
// Little endian
return ((b4 << 24) | (b3 << 16) | (b2 << 8) | b1);
}
else
return 0;
}
} // namespace dio

44
src/dio/decoder.h Normal file
View File

@ -0,0 +1,44 @@
// Aseprite Document IO Library
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DIO_DECODER_H_INCLUDED
#define DIO_DECODER_H_INCLUDED
#pragma once
#include <cstdint>
namespace doc {
class Document;
}
namespace dio {
class DecodeDelegate;
class FileInterface;
class Decoder {
public:
Decoder();
virtual ~Decoder();
virtual void initialize(DecodeDelegate* delegate, FileInterface* f);
virtual bool decode() = 0;
protected:
DecodeDelegate* delegate() { return m_delegate; }
FileInterface* f() { return m_f; }
uint8_t read8();
uint16_t read16();
uint32_t read32();
private:
DecodeDelegate* m_delegate;
FileInterface* m_f;
};
} // namespace dio
#endif

View File

@ -31,7 +31,8 @@ FileFormat detect_format(const std::string& filename)
return ff;
}
FileFormat detect_format_by_file_content(const std::string& filename)
FileFormat detect_format_by_file_content_bytes(const uint8_t* buf,
const int n)
{
#define IS_MAGIC_WORD(offset, word) \
((buf[offset+0] == (word & 0xff)) && \
@ -43,22 +44,14 @@ FileFormat detect_format_by_file_content(const std::string& filename)
(buf[offset+2] == ((dword & 0xff0000) >> 16)) && \
(buf[offset+3] == ((dword & 0xff000000) >> 24)))
base::FileHandle handle(base::open_file(filename.c_str(), "rb"));
if (!handle)
return FileFormat::ERROR;
FILE* f = handle.get();
unsigned char buf[8];
int count = fread(buf, 1, 8, f);
if (count >= 2) {
if (n >= 2) {
if (IS_MAGIC_WORD(0, BMP_MAGIC_NUMBER))
return FileFormat::BMP_IMAGE;
if (IS_MAGIC_WORD(0, JPG_MAGIC_NUMBER))
return FileFormat::JPEG_IMAGE;
if (count >= 6) {
if (n >= 6) {
if (std::strncmp((const char*)buf, GIF_87_STAMP, 6) == 0 ||
std::strncmp((const char*)buf, GIF_89_STAMP, 6) == 0)
return FileFormat::GIF_ANIMATION;
@ -70,7 +63,7 @@ FileFormat detect_format_by_file_content(const std::string& filename)
IS_MAGIC_WORD(4, FLC_MAGIC_NUMBER))
return FileFormat::FLIC_ANIMATION;
if (count >= 8) {
if (n >= 8) {
if (IS_MAGIC_DWORD(0, PNG_MAGIC_DWORD1) &&
IS_MAGIC_DWORD(4, PNG_MAGIC_DWORD2))
return FileFormat::PNG_IMAGE;
@ -81,6 +74,19 @@ FileFormat detect_format_by_file_content(const std::string& filename)
return FileFormat::UNKNOWN;
}
FileFormat detect_format_by_file_content(const std::string& filename)
{
base::FileHandle handle(base::open_file(filename.c_str(), "rb"));
if (!handle)
return FileFormat::ERROR;
FILE* f = handle.get();
uint8_t buf[8];
int n = (int)fread(buf, 1, 8, f);
return detect_format_by_file_content_bytes(buf, n);
}
FileFormat detect_format_by_file_extension(const std::string& filename)
{
// By extension

View File

@ -10,11 +10,14 @@
#include "dio/file_format.h"
#include <cstdint>
#include <string>
namespace dio {
FileFormat detect_format(const std::string& filename);
FileFormat detect_format_by_file_content_bytes(const uint8_t* buf,
const int n);
FileFormat detect_format_by_file_content(const std::string& filename);
FileFormat detect_format_by_file_extension(const std::string& filename);

55
src/dio/file_interface.h Normal file
View File

@ -0,0 +1,55 @@
// Aseprite Document IO Library
// Copyright (c) 2017-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DIO_FILE_INTERFACE_H_INCLUDED
#define DIO_FILE_INTERFACE_H_INCLUDED
#pragma once
#include <cstddef>
#include <cstdint>
#include <cstdio>
namespace dio {
class FileInterface {
public:
virtual ~FileInterface() { }
// Returns true if we can read/write bytes from/into the file
virtual bool ok() const = 0;
// Current position in the file
virtual size_t tell() = 0;
// Jump to the given position in the file
virtual void seek(size_t absPos) = 0;
// Returns the next byte in the file or 0 if ok() = false
virtual uint8_t read8() = 0;
virtual size_t readBytes(uint8_t* buf, size_t n) = 0;
// Writes one byte in the file (or do nothing if ok() = false)
virtual void write8(uint8_t value) = 0;
};
class StdioFileInterface : public FileInterface {
public:
StdioFileInterface(FILE* file);
bool ok() const override;
size_t tell() override;
void seek(size_t absPos) override;
uint8_t read8() override;
size_t readBytes(uint8_t* buf, size_t n) override;
void write8(uint8_t value) override;
private:
FILE* m_file;
bool m_ok;
};
} // namespace dio
#endif

120
src/dio/pixel_io.h Normal file
View File

@ -0,0 +1,120 @@
// Aseprite Document IO Library
// Copyright (c) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DIO_PIXEL_IO_H_INCLUDED
#define DIO_PIXEL_IO_H_INCLUDED
#pragma once
#include "dio/file_interface.h"
namespace dio {
template<typename ImageTraits>
class PixelIO {
public:
typename ImageTraits::pixel_t read_pixel(FileInterface* fi);
void write_pixel(FileInterface* fi, typename ImageTraits::pixel_t c);
void read_scanline(typename ImageTraits::address_t address,
int w, uint8_t* buffer);
void write_scanline(typename ImageTraits::address_t address,
int w, uint8_t* buffer);
};
template<>
class PixelIO<doc::RgbTraits> {
int r, g, b, a;
public:
doc::RgbTraits::pixel_t read_pixel(FileInterface* f) {
r = f->read8();
g = f->read8();
b = f->read8();
a = f->read8();
return doc::rgba(r, g, b, a);
}
void write_pixel(FileInterface* f, doc::RgbTraits::pixel_t c) {
f->write8(doc::rgba_getr(c));
f->write8(doc::rgba_getr(c));
f->write8(doc::rgba_getg(c));
f->write8(doc::rgba_getb(c));
f->write8(doc::rgba_geta(c));
}
void read_scanline(doc::RgbTraits::address_t address,
int w, uint8_t* buffer) {
for (int x=0; x<w; ++x) {
r = *(buffer++);
g = *(buffer++);
b = *(buffer++);
a = *(buffer++);
*(address++) = doc::rgba(r, g, b, a);
}
}
void write_scanline(doc::RgbTraits::address_t address,
int w, uint8_t* buffer) {
for (int x=0; x<w; ++x) {
*(buffer++) = doc::rgba_getr(*address);
*(buffer++) = doc::rgba_getg(*address);
*(buffer++) = doc::rgba_getb(*address);
*(buffer++) = doc::rgba_geta(*address);
++address;
}
}
};
template<>
class PixelIO<doc::GrayscaleTraits> {
int k, a;
public:
doc::GrayscaleTraits::pixel_t read_pixel(FileInterface* f) {
k = f->read8();
a = f->read8();
return doc::graya(k, a);
}
void write_pixel(FileInterface* f, doc::GrayscaleTraits::pixel_t c) {
f->write8(doc::graya_getv(c));
f->write8(doc::graya_geta(c));
}
void read_scanline(doc::GrayscaleTraits::address_t address,
int w, uint8_t* buffer)
{
for (int x=0; x<w; ++x) {
k = *(buffer++);
a = *(buffer++);
*(address++) = doc::graya(k, a);
}
}
void write_scanline(doc::GrayscaleTraits::address_t address,
int w, uint8_t* buffer)
{
for (int x=0; x<w; ++x) {
*(buffer++) = doc::graya_getv(*address);
*(buffer++) = doc::graya_geta(*address);
++address;
}
}
};
template<>
class PixelIO<doc::IndexedTraits> {
public:
doc::IndexedTraits::pixel_t read_pixel(FileInterface* f) {
return f->read8();
}
void write_pixel(FileInterface* f, doc::IndexedTraits::pixel_t c) {
f->write8(c);
}
void read_scanline(doc::IndexedTraits::address_t address,
int w, uint8_t* buffer) {
memcpy(address, buffer, w);
}
void write_scanline(doc::IndexedTraits::address_t address,
int w, uint8_t* buffer) {
memcpy(buffer, address, w);
}
};
} // namespace dio
#endif

55
src/dio/stdio.cpp Normal file
View File

@ -0,0 +1,55 @@
// Aseprite Document IO Library
// Copyright (c) 2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "dio/file_interface.h"
namespace dio {
StdioFileInterface::StdioFileInterface(FILE* file)
: m_file(file)
, m_ok(true)
{
}
bool StdioFileInterface::ok() const
{
return m_ok;
}
size_t StdioFileInterface::tell()
{
return ftell(m_file);
}
void StdioFileInterface::seek(size_t absPos)
{
fseek(m_file, absPos, SEEK_SET);
}
uint8_t StdioFileInterface::read8()
{
int value = fgetc(m_file);
if (value != EOF)
return value;
m_ok = false;
return 0;
}
size_t StdioFileInterface::readBytes(uint8_t* buf, size_t n)
{
int n2 = fread(buf, 1, n, m_file);
if (n2 != n)
m_ok = false;
return n2;
}
void StdioFileInterface::write8(uint8_t value)
{
fputc(value, m_file);
}
} // namespace dio