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 | |
26 | namespace app { |
27 | |
28 | using namespace base; |
29 | |
30 | class 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 | |
61 | FileFormat* CreateFliFormat() |
62 | { |
63 | return new FliFormat; |
64 | } |
65 | |
66 | bool 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 ; |
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 | |
178 | static 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 | |
207 | bool 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 ; |
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 | |