| 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 | |