| 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 | #ifndef APP_FILE_FILE_H_INCLUDED |
| 9 | #define APP_FILE_FILE_H_INCLUDED |
| 10 | #pragma once |
| 11 | |
| 12 | #include "app/color.h" |
| 13 | #include "app/doc.h" |
| 14 | #include "app/file/file_op_config.h" |
| 15 | #include "app/file/format_options.h" |
| 16 | #include "app/pref/preferences.h" |
| 17 | #include "base/paths.h" |
| 18 | #include "doc/frame.h" |
| 19 | #include "doc/image_ref.h" |
| 20 | #include "doc/pixel_format.h" |
| 21 | #include "doc/selected_frames.h" |
| 22 | #include "os/color_space.h" |
| 23 | |
| 24 | #include <cstdio> |
| 25 | #include <memory> |
| 26 | #include <mutex> |
| 27 | #include <string> |
| 28 | |
| 29 | // Flags for FileOp::createLoadDocumentOperation() |
| 30 | #define FILE_LOAD_SEQUENCE_NONE 0x00000001 |
| 31 | #define FILE_LOAD_SEQUENCE_ASK 0x00000002 |
| 32 | #define FILE_LOAD_SEQUENCE_ASK_CHECKBOX 0x00000004 |
| 33 | #define FILE_LOAD_SEQUENCE_YES 0x00000008 |
| 34 | #define FILE_LOAD_ONE_FRAME 0x00000010 |
| 35 | #define FILE_LOAD_DATA_FILE 0x00000020 |
| 36 | #define FILE_LOAD_CREATE_PALETTE 0x00000040 |
| 37 | |
| 38 | namespace doc { |
| 39 | class Tag; |
| 40 | } |
| 41 | |
| 42 | namespace doc { |
| 43 | class Cel; |
| 44 | class Image; |
| 45 | class Layer; |
| 46 | class LayerImage; |
| 47 | class Palette; |
| 48 | class Slice; |
| 49 | class Sprite; |
| 50 | } |
| 51 | |
| 52 | namespace app { |
| 53 | |
| 54 | class Context; |
| 55 | class FileFormat; |
| 56 | |
| 57 | using namespace doc; |
| 58 | |
| 59 | // File operations. |
| 60 | typedef enum { |
| 61 | FileOpLoad, |
| 62 | FileOpSave |
| 63 | } FileOpType; |
| 64 | |
| 65 | class IFileOpProgress { |
| 66 | public: |
| 67 | virtual ~IFileOpProgress() { } |
| 68 | virtual void ackFileOpProgress(double progress) = 0; |
| 69 | }; |
| 70 | |
| 71 | class FileOpROI { // Region of interest |
| 72 | public: |
| 73 | FileOpROI(); |
| 74 | FileOpROI(const Doc* doc, |
| 75 | const gfx::Rect& bounds, |
| 76 | const std::string& sliceName, |
| 77 | const std::string& tagName, |
| 78 | const doc::SelectedFrames& selFrames, |
| 79 | const bool adjustByTag); |
| 80 | |
| 81 | const Doc* document() const { return m_document; } |
| 82 | const gfx::Rect& bounds() const { return m_bounds; } |
| 83 | doc::Slice* slice() const { return m_slice; } |
| 84 | doc::Tag* tag() const { return m_tag; } |
| 85 | doc::frame_t fromFrame() const { return m_selFrames.firstFrame(); } |
| 86 | doc::frame_t toFrame() const { return m_selFrames.lastFrame(); } |
| 87 | const doc::SelectedFrames& selectedFrames() const { return m_selFrames; } |
| 88 | |
| 89 | doc::frame_t frames() const { |
| 90 | return (doc::frame_t)m_selFrames.size(); |
| 91 | } |
| 92 | |
| 93 | private: |
| 94 | const Doc* m_document; |
| 95 | gfx::Rect m_bounds; |
| 96 | doc::Slice* m_slice; |
| 97 | doc::Tag* m_tag; |
| 98 | doc::SelectedFrames m_selFrames; |
| 99 | }; |
| 100 | |
| 101 | // Used by file formats with FILE_ENCODE_ABSTRACT_IMAGE flag, to |
| 102 | // encode a sprite with an intermediate transformation on-the-fly |
| 103 | // (e.g. resizing). |
| 104 | class FileAbstractImage { |
| 105 | public: |
| 106 | virtual ~FileAbstractImage() { } |
| 107 | |
| 108 | virtual int width() const { return spec().width(); } |
| 109 | virtual int height() const { return spec().height(); } |
| 110 | |
| 111 | virtual const doc::ImageSpec& spec() const = 0; |
| 112 | virtual os::ColorSpaceRef osColorSpace() const = 0; |
| 113 | virtual bool needAlpha() const = 0; |
| 114 | virtual bool isOpaque() const = 0; |
| 115 | virtual int frames() const = 0; |
| 116 | virtual int frameDuration(doc::frame_t frame) const = 0; |
| 117 | |
| 118 | virtual const doc::Palette* palette(doc::frame_t frame) const = 0; |
| 119 | virtual doc::PalettesList palettes() const = 0; |
| 120 | |
| 121 | virtual const doc::ImageRef getScaledImage() const = 0; |
| 122 | |
| 123 | // In case the file format can encode scanline by scanline |
| 124 | // (e.g. PNG format). |
| 125 | virtual const uint8_t* getScanline(int y) const = 0; |
| 126 | |
| 127 | // In case that the encoder needs full frame renders (or compare |
| 128 | // between frames), e.g. GIF format. |
| 129 | virtual void renderFrame(const doc::frame_t frame, doc::Image* dst) const = 0; |
| 130 | }; |
| 131 | |
| 132 | // Structure to load & save files. |
| 133 | // |
| 134 | // TODO This class do to many things. There should be a previous |
| 135 | // instance (class) to verify what the user want to do with the |
| 136 | // sequence of files, and the result of that operation should be the |
| 137 | // input of this one. |
| 138 | class FileOp { |
| 139 | public: |
| 140 | static FileOp* createLoadDocumentOperation(Context* context, |
| 141 | const std::string& filename, |
| 142 | const int flags, |
| 143 | const FileOpConfig* config = nullptr); |
| 144 | |
| 145 | static FileOp* createSaveDocumentOperation(const Context* context, |
| 146 | const FileOpROI& roi, |
| 147 | const std::string& filename, |
| 148 | const std::string& filenameFormat, |
| 149 | const bool ignoreEmptyFrames); |
| 150 | |
| 151 | static bool checkIfFormatSupportResizeOnTheFly(const std::string& filename); |
| 152 | |
| 153 | ~FileOp(); |
| 154 | |
| 155 | bool isSequence() const { return !m_seq.filename_list.empty(); } |
| 156 | bool isOneFrame() const { return m_oneframe; } |
| 157 | bool preserveColorProfile() const { return m_config.preserveColorProfile; } |
| 158 | |
| 159 | const std::string& filename() const { return m_filename; } |
| 160 | const base::paths& filenames() const { return m_seq.filename_list; } |
| 161 | Context* context() const { return m_context; } |
| 162 | Doc* document() const { return m_document; } |
| 163 | Doc* releaseDocument() { |
| 164 | Doc* doc = m_document; |
| 165 | m_document = nullptr; |
| 166 | return doc; |
| 167 | } |
| 168 | |
| 169 | const FileOpROI& roi() const { return m_roi; } |
| 170 | |
| 171 | void createDocument(Sprite* spr); |
| 172 | void operate(IFileOpProgress* progress = nullptr); |
| 173 | |
| 174 | void done(); |
| 175 | void stop(); |
| 176 | bool isDone() const; |
| 177 | bool isStop() const; |
| 178 | |
| 179 | // Does extra post-load processing which may require user intervention. |
| 180 | void postLoad(); |
| 181 | |
| 182 | // Special options specific to the file format. |
| 183 | FormatOptionsPtr formatOptions() const { |
| 184 | return m_formatOptions; |
| 185 | } |
| 186 | |
| 187 | // Options to save the document. This function doesn't return |
| 188 | // nullptr. |
| 189 | template<typename T> |
| 190 | std::shared_ptr<T> formatOptionsForSaving() const { |
| 191 | auto opts = std::dynamic_pointer_cast<T>(m_formatOptions); |
| 192 | if (!opts) |
| 193 | opts = std::make_shared<T>(); |
| 194 | ASSERT(opts); |
| 195 | return opts; |
| 196 | } |
| 197 | |
| 198 | bool hasFormatOptionsOfDocument() const { |
| 199 | return (m_document->formatOptions() != nullptr); |
| 200 | } |
| 201 | |
| 202 | // Options from the document when it was loaded. This function |
| 203 | // doesn't return nullptr. |
| 204 | template<typename T> |
| 205 | std::shared_ptr<T> formatOptionsOfDocument() const { |
| 206 | // We use the dynamic cast because the document format options |
| 207 | // could be an instance of another class than T. |
| 208 | auto opts = std::dynamic_pointer_cast<T>(m_document->formatOptions()); |
| 209 | if (!opts) { |
| 210 | // If the document doesn't have format options (or the type |
| 211 | // doesn't matches T), we create default format options for |
| 212 | // this file. |
| 213 | opts = std::make_shared<T>(); |
| 214 | } |
| 215 | ASSERT(opts); |
| 216 | return opts; |
| 217 | } |
| 218 | |
| 219 | void setLoadedFormatOptions(const FormatOptionsPtr& opts); |
| 220 | |
| 221 | // Helpers for file decoder/encoder (FileFormat) with |
| 222 | // FILE_SUPPORT_SEQUENCES flag. |
| 223 | void sequenceSetNColors(int ncolors); |
| 224 | int sequenceGetNColors() const; |
| 225 | void sequenceSetColor(int index, int r, int g, int b); |
| 226 | void sequenceGetColor(int index, int* r, int* g, int* b) const; |
| 227 | void sequenceSetAlpha(int index, int a); |
| 228 | void sequenceGetAlpha(int index, int* a) const; |
| 229 | ImageRef sequenceImage(PixelFormat pixelFormat, int w, int h); |
| 230 | const ImageRef sequenceImage() const { return m_seq.image; } |
| 231 | const Palette* sequenceGetPalette() const { return m_seq.palette; } |
| 232 | bool sequenceGetHasAlpha() const { |
| 233 | return m_seq.has_alpha; |
| 234 | } |
| 235 | void sequenceSetHasAlpha(bool hasAlpha) { |
| 236 | m_seq.has_alpha = hasAlpha; |
| 237 | } |
| 238 | int sequenceFlags() const { |
| 239 | return m_seq.flags; |
| 240 | } |
| 241 | |
| 242 | // Can be used to encode sequences/static files (e.g. png files) |
| 243 | // or animations (e.g. gif) resizing the result on the fly. |
| 244 | FileAbstractImage* abstractImage(); |
| 245 | void setOnTheFlyScale(const gfx::PointF& scale); |
| 246 | |
| 247 | const std::string& error() const { return m_error; } |
| 248 | void setError(const char *error, ...); |
| 249 | bool hasError() const { return !m_error.empty(); } |
| 250 | |
| 251 | double progress() const; |
| 252 | void setProgress(double progress); |
| 253 | |
| 254 | void getFilenameList(base::paths& output) const; |
| 255 | |
| 256 | void setEmbeddedColorProfile() { m_embeddedColorProfile = true; } |
| 257 | bool hasEmbeddedColorProfile() const { return m_embeddedColorProfile; } |
| 258 | |
| 259 | void setEmbeddedGridBounds() { m_embeddedGridBounds = true; } |
| 260 | bool hasEmbeddedGridBounds() const { return m_embeddedGridBounds; } |
| 261 | |
| 262 | bool newBlend() const { return m_config.newBlend; } |
| 263 | |
| 264 | private: |
| 265 | FileOp(); // Undefined |
| 266 | FileOp(FileOpType type, |
| 267 | Context* context, |
| 268 | const FileOpConfig* config); |
| 269 | |
| 270 | FileOpType m_type; // Operation type: 0=load, 1=save. |
| 271 | FileFormat* m_format; |
| 272 | Context* m_context; |
| 273 | // TODO this should be a shared pointer (and we should remove |
| 274 | // releaseDocument() member function) |
| 275 | Doc* m_document; // Loaded document, or document to be saved. |
| 276 | std::string m_filename; // File-name to load/save. |
| 277 | std::string m_dataFilename; // File-name for a special XML .aseprite-data where extra sprite data can be stored |
| 278 | FileOpROI m_roi; |
| 279 | |
| 280 | // Shared fields between threads. |
| 281 | mutable std::mutex m_mutex; // Mutex to access to the next two fields. |
| 282 | double m_progress; // Progress (1.0 is ready). |
| 283 | IFileOpProgress* m_progressInterface; |
| 284 | std::string m_error; // Error string. |
| 285 | bool m_done; // True if the operation finished. |
| 286 | bool m_stop; // Force the break of the operation. |
| 287 | bool m_oneframe; // Load just one frame (in formats |
| 288 | // that support animation like |
| 289 | // GIF/FLI/ASE). |
| 290 | bool m_createPaletteFromRgba; |
| 291 | bool m_ignoreEmpty; |
| 292 | |
| 293 | // True if the file contained a color profile when it was loaded. |
| 294 | bool m_embeddedColorProfile; |
| 295 | |
| 296 | // True if the file contained a the grid bounds inside. |
| 297 | bool m_embeddedGridBounds; |
| 298 | |
| 299 | FileOpConfig m_config; |
| 300 | |
| 301 | // Options |
| 302 | FormatOptionsPtr m_formatOptions; |
| 303 | |
| 304 | // Data for sequences. |
| 305 | struct { |
| 306 | base::paths filename_list; // All file names to load/save. |
| 307 | Palette* palette; // Palette of the sequence. |
| 308 | ImageRef image; // Image to be saved/loaded. |
| 309 | // For the progress bar. |
| 310 | double progress_offset; // Progress offset from the current frame. |
| 311 | double progress_fraction; // Progress fraction for one frame. |
| 312 | // To load sequences. |
| 313 | frame_t frame; |
| 314 | bool has_alpha; |
| 315 | LayerImage* layer; |
| 316 | Cel* last_cel; |
| 317 | int duration; |
| 318 | // Flags after the user choose what to do with the sequence. |
| 319 | int flags; |
| 320 | } m_seq; |
| 321 | |
| 322 | class FileAbstractImageImpl; |
| 323 | std::unique_ptr<FileAbstractImageImpl> m_abstractImage; |
| 324 | |
| 325 | void prepareForSequence(); |
| 326 | void makeAbstractImage(); |
| 327 | }; |
| 328 | |
| 329 | // Available extensions for each load/save operation. |
| 330 | base::paths get_readable_extensions(); |
| 331 | base::paths get_writable_extensions(const int requiredFormatFlag = 0); |
| 332 | |
| 333 | // High-level routines to load/save documents. |
| 334 | Doc* load_document(Context* context, const std::string& filename); |
| 335 | int save_document(Context* context, Doc* document); |
| 336 | |
| 337 | // Returns true if the given filename contains a file extension that |
| 338 | // can be used to save only static images (i.e. animations are saved |
| 339 | // as sequence of files). |
| 340 | bool is_static_image_format(const std::string& filename); |
| 341 | |
| 342 | } // namespace app |
| 343 | |
| 344 | #endif |
| 345 | |