aseprite/src/app/file/fli_format.cpp

278 lines
7.1 KiB
C++

// Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/doc.h"
#include "app/file/file.h"
#include "app/file/file_format.h"
#include "app/file/format_options.h"
#include "app/modules/palettes.h"
#include "app/pref/preferences.h"
#include "base/file_handle.h"
#include "doc/doc.h"
#include "flic/flic.h"
#include "render/render.h"
#include <algorithm>
#include <cstdio>
namespace app {
using namespace base;
class FliFormat : public FileFormat {
const char* onGetName() const override {
return "flc";
}
void onGetExtensions(base::paths& exts) const override {
exts.push_back("flc");
exts.push_back("fli");
}
dio::FileFormat onGetDioFormat() const override {
return dio::FileFormat::FLIC_ANIMATION;
}
int onGetFlags() const override {
return
FILE_SUPPORT_LOAD |
FILE_SUPPORT_SAVE |
FILE_SUPPORT_INDEXED |
FILE_SUPPORT_FRAMES |
FILE_SUPPORT_PALETTES |
FILE_ENCODE_ABSTRACT_IMAGE;
}
bool onLoad(FileOp* fop) override;
#ifdef ENABLE_SAVE
bool onSave(FileOp* fop) override;
#endif
};
FileFormat* CreateFliFormat()
{
return new FliFormat;
}
bool FliFormat::onLoad(FileOp* fop)
{
// Open the file to read in binary mode
FileHandle handle(open_file_with_exception(fop->filename(), "rb"));
FILE* f = handle.get();
flic::StdioFileInterface finterface(f);
flic::Decoder decoder(&finterface);
flic::Header header;
if (!decoder.readHeader(header)) {
fop->setError("The file doesn't have a FLIC header\n");
return false;
}
// Size by frame
const int w = header.width;
const int h = header.height;
ASSERT(w > 0 && h > 0); // The decoder cannot return invalid widht/height values
if (w > 10000 || h > 10000) {
fop->setError("Image size too big: %dx%d not suported\n", w, h);
return false;
}
// Create a temporal bitmap
ImageRef bmp(Image::create(IMAGE_INDEXED, w, h));
Palette pal(0, 1);
Cel* prevCel = nullptr;
// Create the sprite
Sprite* sprite = new Sprite(ImageSpec(ColorMode::INDEXED, w, h), 256);
LayerImage* layer = new LayerImage(sprite);
sprite->root()->addLayer(layer);
layer->configureAsBackground();
// Set frames and speed
sprite->setTotalFrames(frame_t(header.frames));
sprite->setDurationForAllFrames(header.speed);
flic::Frame fliFrame;
flic::Colormap oldFliColormap;
fliFrame.pixels = bmp->getPixelAddress(0, 0);
fliFrame.rowstride = bmp->rowBytes();
frame_t frame_out = 0;
for (frame_t frame_in=0;
frame_in<sprite->totalFrames();
++frame_in) {
// Read the frame
if (!decoder.readFrame(fliFrame)) {
fop->setError("Error reading frame %d\n", frame_in);
continue;
}
// Palette change
bool palChange = false;
if (frame_out == 0 || oldFliColormap != fliFrame.colormap) {
oldFliColormap = fliFrame.colormap;
pal.resize(fliFrame.colormap.size());
for (int c=0; c<int(fliFrame.colormap.size()); c++) {
pal.setEntry(c, rgba(fliFrame.colormap[c].r,
fliFrame.colormap[c].g,
fliFrame.colormap[c].b, 255));
}
pal.setFrame(frame_out);
sprite->setPalette(&pal, true);
palChange = true;
}
// First frame, or the frame changes
if (!prevCel ||
(count_diff_between_images(prevCel->image(), bmp.get()))) {
// Add the new frame
ImageRef image(Image::createCopy(bmp.get()));
Cel* cel = new Cel(frame_out, image);
layer->addCel(cel);
prevCel = cel;
++frame_out;
}
else if (palChange) {
Cel* cel = Cel::MakeLink(frame_out, prevCel);
layer->addCel(cel);
++frame_out;
}
// The palette and the image don't change: add duration to the last added frame
else {
sprite->setFrameDuration(
frame_out-1, sprite->frameDuration(frame_out-1) + header.speed);
}
if (header.frames > 0)
fop->setProgress((float)(frame_in+1) / (float)(header.frames));
if (fop->isStop())
break;
if (fop->isOneFrame())
break;
}
if (frame_out > 0)
sprite->setTotalFrames(frame_out);
fop->createDocument(sprite);
return true;
}
#ifdef ENABLE_SAVE
static int get_time_precision(const FileAbstractImage* sprite,
const doc::FramesSequence& framesSeq)
{
// Check if all frames have the same duration
bool constantFrameRate = true;
frame_t prevFrame = -1;
for (frame_t frame : framesSeq) {
if (prevFrame >= 0) {
if (sprite->frameDuration(prevFrame) != sprite->frameDuration(frame)) {
constantFrameRate = false;
break;
}
}
prevFrame = frame;
}
if (constantFrameRate)
return sprite->frameDuration(0);
int precision = 1000;
for (frame_t frame : framesSeq) {
int len = sprite->frameDuration(frame);
while (len / precision == 0)
precision /= 10;
if (precision <= 1)
break;
}
return precision;
}
bool FliFormat::onSave(FileOp* fop)
{
const FileAbstractImage* sprite = fop->abstractImageToSave();
// Open the file to write in binary mode
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
FILE* f = handle.get();
flic::StdioFileInterface finterface(f);
flic::Encoder encoder(&finterface);
flic::Header header;
header.frames = 0;
header.width = sprite->width();
header.height = sprite->height();
header.speed = get_time_precision(sprite, fop->roi().framesSequence());
encoder.writeHeader(header);
// Create the bitmaps
ImageRef bmp(Image::create(IMAGE_INDEXED, sprite->width(), sprite->height()));
render::Render render;
render.setNewBlend(fop->newBlend());
// Write frame by frame
flic::Frame fliFrame;
fliFrame.pixels = bmp->getPixelAddress(0, 0);
fliFrame.rowstride = bmp->rowBytes();
auto frame_beg = fop->roi().framesSequence().begin();
auto frame_end = fop->roi().framesSequence().end();
auto frame_it = frame_beg;
frame_t nframes = fop->roi().frames();
for (frame_t f=0; f<=nframes; ++f, ++frame_it) {
if (frame_it == frame_end)
frame_it = frame_beg;
frame_t frame = *frame_it;
const Palette* pal = sprite->palette(frame);
int size = std::min(256, pal->size());
for (int c=0; c<size; c++) {
color_t color = pal->getEntry(c);
fliFrame.colormap[c].r = rgba_getr(color);
fliFrame.colormap[c].g = rgba_getg(color);
fliFrame.colormap[c].b = rgba_getb(color);
}
// Render the frame in the bitmap
sprite->renderFrame(frame, fop->roi().frameBounds(frame), bmp.get());
// How many times this frame should be written to get the same
// time that it has in the sprite
if (f < nframes) {
int times = sprite->frameDuration(frame) / header.speed;
times = std::max(1, times);
for (int c=0; c<times; c++)
encoder.writeFrame(fliFrame);
}
else {
encoder.writeRingFrame(fliFrame);
}
// Update progress
fop->setProgress((float)(f+1) / (float)(nframes+1));
}
return true;
}
#endif // ENABLE_SAVE
} // namespace app