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