mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-10 03:44:16 +00:00
870 lines
23 KiB
C++
870 lines
23 KiB
C++
// 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 "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,
|
|
¤t_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(2);
|
|
uint32_t nchunks = read32();
|
|
|
|
if (frame_header->chunks == 0xFFFF &&
|
|
frame_header->chunks < nchunks)
|
|
frame_header->chunks = nchunks;
|
|
}
|
|
|
|
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 &
|
|
static_cast<int>(doc::LayerFlags::PersistentFlagsMask)));
|
|
|
|
// 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) {
|
|
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 = ((int32_t)read32());
|
|
bounds.y = ((int32_t)read32());
|
|
bounds.w = read32();
|
|
bounds.h = read32();
|
|
|
|
if (flags & ASE_SLICE_FLAG_HAS_CENTER_BOUNDS) {
|
|
center.x = ((int32_t)read32());
|
|
center.y = ((int32_t)read32());
|
|
center.w = read32();
|
|
center.h = read32();
|
|
}
|
|
|
|
if (flags & ASE_SLICE_FLAG_HAS_PIVOT_POINT) {
|
|
pivot.x = ((int32_t)read32());
|
|
pivot.y = ((int32_t)read32());
|
|
}
|
|
|
|
slice->insert(frame, doc::SliceKey(bounds, center, pivot));
|
|
}
|
|
|
|
slices.add(slice);
|
|
return slice.release();
|
|
}
|
|
|
|
} // namespace dio
|