mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-10 12:44:53 +00:00
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:
parent
11817e27fe
commit
cf07af155f
@ -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
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
82
src/dio/aseprite_common.h
Normal 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
|
864
src/dio/aseprite_decoder.cpp
Normal file
864
src/dio/aseprite_decoder.cpp
Normal 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,
|
||||
¤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(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
|
64
src/dio/aseprite_decoder.h
Normal file
64
src/dio/aseprite_decoder.h
Normal 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
51
src/dio/decode_delegate.h
Normal 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
47
src/dio/decode_file.cpp
Normal 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
21
src/dio/decode_file.h
Normal 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
62
src/dio/decoder.cpp
Normal 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
44
src/dio/decoder.h
Normal 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
|
@ -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
|
||||
|
@ -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
55
src/dio/file_interface.h
Normal 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
120
src/dio/pixel_io.h
Normal 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
55
src/dio/stdio.cpp
Normal 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
|
Loading…
x
Reference in New Issue
Block a user