1// Aseprite
2// Copyright (C) 2018-2022 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/doc.h"
13#include "app/file/file.h"
14#include "app/file/file_format.h"
15#include "app/file/format_options.h"
16#include "app/modules/palettes.h"
17#include "app/pref/preferences.h"
18#include "base/file_handle.h"
19#include "doc/doc.h"
20#include "flic/flic.h"
21#include "render/render.h"
22
23#include <algorithm>
24#include <cstdio>
25
26namespace app {
27
28using namespace base;
29
30class FliFormat : public FileFormat {
31
32 const char* onGetName() const override {
33 return "flc";
34 }
35
36 void onGetExtensions(base::paths& exts) const override {
37 exts.push_back("flc");
38 exts.push_back("fli");
39 }
40
41 dio::FileFormat onGetDioFormat() const override {
42 return dio::FileFormat::FLIC_ANIMATION;
43 }
44
45 int onGetFlags() const override {
46 return
47 FILE_SUPPORT_LOAD |
48 FILE_SUPPORT_SAVE |
49 FILE_SUPPORT_INDEXED |
50 FILE_SUPPORT_FRAMES |
51 FILE_SUPPORT_PALETTES |
52 FILE_ENCODE_ABSTRACT_IMAGE;
53 }
54
55 bool onLoad(FileOp* fop) override;
56#ifdef ENABLE_SAVE
57 bool onSave(FileOp* fop) override;
58#endif
59};
60
61FileFormat* CreateFliFormat()
62{
63 return new FliFormat;
64}
65
66bool FliFormat::onLoad(FileOp* fop)
67{
68 // Open the file to read in binary mode
69 FileHandle handle(open_file_with_exception(fop->filename(), "rb"));
70 FILE* f = handle.get();
71 flic::StdioFileInterface finterface(f);
72 flic::Decoder decoder(&finterface);
73
74 flic::Header header;
75 if (!decoder.readHeader(header)) {
76 fop->setError("The file doesn't have a FLIC header\n");
77 return false;
78 }
79
80 // Size by frame
81 const int w = header.width;
82 const int h = header.height;
83 ASSERT(w > 0 && h > 0); // The decoder cannot return invalid widht/height values
84 if (w > 10000 || h > 10000) {
85 fop->setError("Image size too big: %dx%d not suported\n", w, h);
86 return false;
87 }
88
89 // Create a temporal bitmap
90 ImageRef bmp(Image::create(IMAGE_INDEXED, w, h));
91 Palette pal(0, 1);
92 Cel* prevCel = nullptr;
93
94 // Create the sprite
95 Sprite* sprite = new Sprite(ImageSpec(ColorMode::INDEXED, w, h), 256);
96 LayerImage* layer = new LayerImage(sprite);
97 sprite->root()->addLayer(layer);
98 layer->configureAsBackground();
99
100 // Set frames and speed
101 sprite->setTotalFrames(frame_t(header.frames));
102 sprite->setDurationForAllFrames(header.speed);
103
104 flic::Frame fliFrame;
105 flic::Colormap oldFliColormap;
106 fliFrame.pixels = bmp->getPixelAddress(0, 0);
107 fliFrame.rowstride = IndexedTraits::getRowStrideBytes(bmp->width());
108
109 frame_t frame_out = 0;
110 for (frame_t frame_in=0;
111 frame_in<sprite->totalFrames();
112 ++frame_in) {
113 // Read the frame
114 if (!decoder.readFrame(fliFrame)) {
115 fop->setError("Error reading frame %d\n", frame_in);
116 continue;
117 }
118
119 // Palette change
120 bool palChange = false;
121 if (frame_out == 0 || oldFliColormap != fliFrame.colormap) {
122 oldFliColormap = fliFrame.colormap;
123
124 pal.resize(fliFrame.colormap.size());
125 for (int c=0; c<int(fliFrame.colormap.size()); c++) {
126 pal.setEntry(c, rgba(fliFrame.colormap[c].r,
127 fliFrame.colormap[c].g,
128 fliFrame.colormap[c].b, 255));
129 }
130 pal.setFrame(frame_out);
131 sprite->setPalette(&pal, true);
132
133 palChange = true;
134 }
135
136 // First frame, or the frame changes
137 if (!prevCel ||
138 (count_diff_between_images(prevCel->image(), bmp.get()))) {
139 // Add the new frame
140 ImageRef image(Image::createCopy(bmp.get()));
141 Cel* cel = new Cel(frame_out, image);
142 layer->addCel(cel);
143
144 prevCel = cel;
145 ++frame_out;
146 }
147 else if (palChange) {
148 Cel* cel = Cel::MakeLink(frame_out, prevCel);
149 layer->addCel(cel);
150
151 ++frame_out;
152 }
153 // The palette and the image don't change: add duration to the last added frame
154 else {
155 sprite->setFrameDuration(
156 frame_out-1, sprite->frameDuration(frame_out-1) + header.speed);
157 }
158
159 if (header.frames > 0)
160 fop->setProgress((float)(frame_in+1) / (float)(header.frames));
161
162 if (fop->isStop())
163 break;
164
165 if (fop->isOneFrame())
166 break;
167 }
168
169 if (frame_out > 0)
170 sprite->setTotalFrames(frame_out);
171
172 fop->createDocument(sprite);
173 return true;
174}
175
176#ifdef ENABLE_SAVE
177
178static int get_time_precision(const FileAbstractImage* sprite,
179 const doc::SelectedFrames& selFrames)
180{
181 // Check if all frames have the same duration
182 bool constantFrameRate = true;
183 frame_t prevFrame = -1;
184 for (frame_t frame : selFrames) {
185 if (prevFrame >= 0) {
186 if (sprite->frameDuration(prevFrame) != sprite->frameDuration(frame)) {
187 constantFrameRate = false;
188 break;
189 }
190 }
191 prevFrame = frame;
192 }
193 if (constantFrameRate)
194 return sprite->frameDuration(0);
195
196 int precision = 1000;
197 for (frame_t frame : selFrames) {
198 int len = sprite->frameDuration(frame);
199 while (len / precision == 0)
200 precision /= 10;
201 if (precision <= 1)
202 break;
203 }
204 return precision;
205}
206
207bool FliFormat::onSave(FileOp* fop)
208{
209 const FileAbstractImage* sprite = fop->abstractImage();
210
211 // Open the file to write in binary mode
212 FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
213 FILE* f = handle.get();
214 flic::StdioFileInterface finterface(f);
215 flic::Encoder encoder(&finterface);
216
217 flic::Header header;
218 header.frames = 0;
219 header.width = sprite->width();
220 header.height = sprite->height();
221 header.speed = get_time_precision(sprite, fop->roi().selectedFrames());
222 encoder.writeHeader(header);
223
224 // Create the bitmaps
225 ImageRef bmp(Image::create(IMAGE_INDEXED, sprite->width(), sprite->height()));
226 render::Render render;
227 render.setNewBlend(fop->newBlend());
228
229 // Write frame by frame
230 flic::Frame fliFrame;
231 fliFrame.pixels = bmp->getPixelAddress(0, 0);
232 fliFrame.rowstride = IndexedTraits::getRowStrideBytes(bmp->width());
233
234 auto frame_beg = fop->roi().selectedFrames().begin();
235 auto frame_end = fop->roi().selectedFrames().end();
236 auto frame_it = frame_beg;
237 frame_t nframes = fop->roi().frames();
238 for (frame_t f=0; f<=nframes; ++f, ++frame_it) {
239 if (frame_it == frame_end)
240 frame_it = frame_beg;
241
242 frame_t frame = *frame_it;
243 const Palette* pal = sprite->palette(frame);
244 int size = std::min(256, pal->size());
245
246 for (int c=0; c<size; c++) {
247 color_t color = pal->getEntry(c);
248 fliFrame.colormap[c].r = rgba_getr(color);
249 fliFrame.colormap[c].g = rgba_getg(color);
250 fliFrame.colormap[c].b = rgba_getb(color);
251 }
252
253 // Render the frame in the bitmap
254 sprite->renderFrame(frame, bmp.get());
255
256 // How many times this frame should be written to get the same
257 // time that it has in the sprite
258 if (f < nframes) {
259 int times = sprite->frameDuration(frame) / header.speed;
260 times = std::max(1, times);
261 for (int c=0; c<times; c++)
262 encoder.writeFrame(fliFrame);
263 }
264 else {
265 encoder.writeRingFrame(fliFrame);
266 }
267
268 // Update progress
269 fop->setProgress((float)(f+1) / (float)(nframes+1));
270 }
271
272 return true;
273}
274
275#endif // ENABLE_SAVE
276
277} // namespace app
278