aseprite/src/app/file/file.cpp

1065 lines
28 KiB
C++
Raw Normal View History

2015-02-12 12:16:25 -03:00
// Aseprite
// Copyright (C) 2001-2017 David Capello
2015-02-12 12:16:25 -03:00
//
2016-08-26 17:02:58 -03:00
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
2007-09-18 23:57:02 +00:00
#ifdef HAVE_CONFIG_H
2007-09-18 23:57:02 +00:00
#include "config.h"
#endif
2007-09-18 23:57:02 +00:00
#include "app/file/file.h"
#include "app/console.h"
#include "app/context.h"
#include "app/document.h"
#include "app/file/file_data.h"
#include "app/file/file_format.h"
#include "app/file/file_formats_manager.h"
#include "app/file/format_options.h"
#include "app/file/split_filename.h"
#include "app/filename_formatter.h"
#include "app/modules/gui.h"
#include "app/modules/palettes.h"
#include "app/ui/status_bar.h"
#include "base/fs.h"
#include "base/mutex.h"
#include "base/scoped_lock.h"
#include "base/shared_ptr.h"
#include "base/string.h"
#include "doc/doc.h"
#include "docio/detect_format.h"
#include "render/quantization.h"
#include "render/render.h"
2012-06-17 22:49:58 -03:00
#include "ui/alert.h"
2007-09-18 23:57:02 +00:00
#include "open_sequence.xml.h"
#include <cstring>
#include <cstdarg>
namespace app {
using namespace base;
std::string get_readable_extensions()
2007-09-18 23:57:02 +00:00
{
std::string buf;
2007-09-18 23:57:02 +00:00
for (const FileFormat* format : *FileFormatsManager::instance()) {
if (format->support(FILE_SUPPORT_LOAD)) {
if (!buf.empty())
buf.push_back(',');
buf += format->extensions();
2011-01-16 17:27:18 -03:00
}
2007-09-18 23:57:02 +00:00
}
return buf;
2007-09-18 23:57:02 +00:00
}
std::string get_writable_extensions()
2007-09-18 23:57:02 +00:00
{
std::string buf;
2007-09-18 23:57:02 +00:00
for (const FileFormat* format : *FileFormatsManager::instance()) {
if (format->support(FILE_SUPPORT_SAVE)) {
if (!buf.empty())
buf.push_back(',');
buf += format->extensions();
2011-01-16 17:27:18 -03:00
}
2007-09-18 23:57:02 +00:00
}
return buf;
2007-09-18 23:57:02 +00:00
}
Document* load_document(Context* context, const std::string& filename)
2007-09-18 23:57:02 +00:00
{
/* TODO add a option to configure what to do with the sequence */
2015-09-29 11:27:00 -03:00
base::UniquePtr<FileOp> fop(FileOp::createLoadDocumentOperation(context, filename, FILE_LOAD_SEQUENCE_NONE));
if (!fop)
2015-09-29 11:27:00 -03:00
return nullptr;
2015-09-29 11:27:00 -03:00
// Operate in this same thread
fop->operate();
fop->done();
fop->postLoad();
2015-09-29 11:27:00 -03:00
if (fop->hasError()) {
Console console(context);
2015-09-29 11:27:00 -03:00
console.printf(fop->error().c_str());
}
2015-09-29 11:27:00 -03:00
Document* document = fop->releaseDocument();
fop.release();
if (document && context)
document->setContext(context);
return document;
}
int save_document(Context* context, doc::Document* document)
{
ASSERT(dynamic_cast<app::Document*>(document));
2015-09-29 11:27:00 -03:00
UniquePtr<FileOp> fop(
FileOp::createSaveDocumentOperation(
context,
FileOpROI(static_cast<app::Document*>(document),
"", "", SelectedFrames(), false),
document->filename(), ""));
if (!fop)
return -1;
2015-09-29 11:27:00 -03:00
// Operate in this same thread
fop->operate();
fop->done();
2015-09-29 11:27:00 -03:00
if (fop->hasError()) {
Console console(context);
2015-09-29 11:27:00 -03:00
console.printf(fop->error().c_str());
}
2015-09-29 11:27:00 -03:00
return (!fop->hasError() ? 0: -1);
}
bool is_static_image_format(const std::string& filename)
{
// Get the format through the extension of the filename
2016-10-27 12:51:06 -03:00
FileFormat* format =
FileFormatsManager::instance()
->getFileFormat(docio::detect_format_by_file_extension(filename));
return (format && format->support(FILE_SUPPORT_SEQUENCES));
}
FileOpROI::FileOpROI()
: m_document(nullptr)
, m_slice(nullptr)
, m_frameTag(nullptr)
{
}
FileOpROI::FileOpROI(const app::Document* doc,
const std::string& sliceName,
const std::string& frameTagName,
const doc::SelectedFrames& selFrames,
const bool adjustByFrameTag)
: m_document(doc)
, m_slice(nullptr)
, m_frameTag(nullptr)
, m_selFrames(selFrames)
{
if (doc) {
if (!sliceName.empty())
m_slice = doc->sprite()->slices().getByName(sliceName);
m_frameTag = doc->sprite()->frameTags().getByName(frameTagName);
if (m_frameTag) {
if (m_selFrames.empty())
m_selFrames.insert(m_frameTag->fromFrame(), m_frameTag->toFrame());
else if (adjustByFrameTag)
m_selFrames.displace(m_frameTag->fromFrame());
m_selFrames.filter(MAX(0, m_frameTag->fromFrame()),
MIN(m_frameTag->toFrame(), doc->sprite()->lastFrame()));
}
// All frames if selected frames is empty
else if (m_selFrames.empty())
m_selFrames.insert(0, doc->sprite()->lastFrame());
}
}
2015-09-29 11:27:00 -03:00
// static
FileOp* FileOp::createLoadDocumentOperation(Context* context, const std::string& filename, int flags)
{
2015-09-29 11:27:00 -03:00
base::UniquePtr<FileOp> fop(
new FileOp(FileOpLoad, context));
if (!fop)
2015-09-29 11:27:00 -03:00
return nullptr;
LOG("FILE: Loading file \"%s\"\n", filename.c_str());
2007-09-18 23:57:02 +00:00
// Does file exist?
if (!base::is_file(filename)) {
fop->setError("File not found: \"%s\"\n", filename.c_str());
goto done;
2007-09-18 23:57:02 +00:00
}
// Get the format through the extension of the filename
fop->m_format = FileFormatsManager::instance()->getFileFormat(
docio::detect_format(filename));
2015-09-29 11:27:00 -03:00
if (!fop->m_format ||
!fop->m_format->support(FILE_SUPPORT_LOAD)) {
fop->setError("%s can't load \"%s\" file (\"%s\")\n", PACKAGE,
filename.c_str(), base::get_file_extension(filename).c_str());
goto done;
}
2007-09-18 23:57:02 +00:00
/* use the "sequence" interface */
2015-09-29 11:27:00 -03:00
if (fop->m_format->support(FILE_SUPPORT_SEQUENCES)) {
/* prepare to load a sequence */
2015-09-29 11:27:00 -03:00
fop->prepareForSequence();
fop->m_seq.flags = flags;
2007-09-18 23:57:02 +00:00
/* per now, we want load just one file */
2015-09-29 11:27:00 -03:00
fop->m_seq.filename_list.push_back(filename);
2007-09-18 23:57:02 +00:00
/* don't load the sequence (just the one file/one frame) */
if (!(flags & FILE_LOAD_SEQUENCE_NONE)) {
std::string left, right;
int c, width, start_from;
char buf[512];
// First of all, we must generate the list of files to load in the
// sequence...
// Check is this could be a sequence
start_from = split_filename(filename, left, right, width);
if (start_from >= 0) {
// Try to get more file names
for (c=start_from+1; ; c++) {
// Get the next file name
sprintf(buf, "%s%0*d%s", left.c_str(), width, c, right.c_str());
// If the file doesn't exist, we doesn't need more files to load
if (!base::is_file(buf))
break;
/* add this file name to the list */
2015-09-29 11:27:00 -03:00
fop->m_seq.filename_list.push_back(buf);
}
2007-09-18 23:57:02 +00:00
}
// TODO add a better dialog to edit file-names
if ((flags & FILE_LOAD_SEQUENCE_ASK) &&
context &&
context->isUIAvailable() &&
fop->m_seq.filename_list.size() > 1) {
app::gen::OpenSequence window;
window.repeat()->setVisible(flags & FILE_LOAD_SEQUENCE_ASK_CHECKBOX ? true: false);
for (const auto& fn : fop->m_seq.filename_list) {
auto item = new ui::ListItem(base::get_file_name(fn));
item->setSelected(true);
window.files()->addChild(item);
}
window.files()->Change.connect(
[&window]{
window.agree()->setEnabled(
window.files()->getSelectedChild() != nullptr);
});
window.openWindowInForeground();
// If the user selected the "do the same for other files"
// checkbox, we've to save what the user want to do for the
// following files.
if (window.repeat()->isSelected()) {
if (window.closer() == window.agree())
fop->m_seq.flags = FILE_LOAD_SEQUENCE_YES;
else
fop->m_seq.flags = FILE_LOAD_SEQUENCE_NONE;
}
if (window.closer() == window.agree()) {
// If the user replies "Agree", we load the selected files.
std::vector<std::string> list;
auto it = window.files()->children().begin();
auto end = window.files()->children().end();
for (const auto& fn : fop->m_seq.filename_list) {
ASSERT(it != end);
if (it == end)
break;
if ((*it)->isSelected())
list.push_back(fn);
++it;
}
ASSERT(!list.empty());
fop->m_seq.filename_list = list;
}
else {
// If the user replies "Skip", we need just one file name
// (the first one).
2015-09-29 11:27:00 -03:00
if (fop->m_seq.filename_list.size() > 1) {
fop->m_seq.filename_list.erase(fop->m_seq.filename_list.begin()+1,
fop->m_seq.filename_list.end());
}
}
2007-09-18 23:57:02 +00:00
}
}
}
else {
2015-09-29 11:27:00 -03:00
fop->m_filename = filename;
}
2007-09-18 23:57:02 +00:00
// Load just one frame
if (flags & FILE_LOAD_ONE_FRAME)
2015-09-29 11:27:00 -03:00
fop->m_oneframe = true;
// Does data file exist?
if (flags & FILE_LOAD_DATA_FILE) {
std::string dataFilename = base::replace_extension(filename, "aseprite-data");
if (base::is_file(dataFilename))
fop->m_dataFilename = dataFilename;
}
done:;
2015-09-29 11:27:00 -03:00
return fop.release();
2007-09-18 23:57:02 +00:00
}
2015-09-29 11:27:00 -03:00
// static
FileOp* FileOp::createSaveDocumentOperation(const Context* context,
const FileOpROI& roi,
const std::string& filename,
const std::string& filenameFormatArg)
2007-09-18 23:57:02 +00:00
{
2015-09-29 11:27:00 -03:00
base::UniquePtr<FileOp> fop(
new FileOp(FileOpSave, const_cast<Context*>(context)));
// Document to save
fop->m_document = const_cast<Document*>(roi.document());
fop->m_roi = roi;
// Get the extension of the filename (in lower case)
LOG("FILE: Saving document \"%s\"\n", filename.c_str());
2007-09-18 23:57:02 +00:00
// Get the format through the extension of the filename
fop->m_format = FileFormatsManager::instance()->getFileFormat(
docio::detect_format_by_file_extension(filename));
2015-09-29 11:27:00 -03:00
if (!fop->m_format ||
!fop->m_format->support(FILE_SUPPORT_SAVE)) {
fop->setError("%s can't save \"%s\" file (\"%s\")\n", PACKAGE,
filename.c_str(), base::get_file_extension(filename).c_str());
2015-09-29 11:27:00 -03:00
return fop.release();
2007-09-18 23:57:02 +00:00
}
// Warnings
std::string warnings;
2015-09-29 11:27:00 -03:00
bool fatal = false;
2007-09-18 23:57:02 +00:00
2016-05-31 16:53:30 -03:00
// Check image type support
2016-07-05 11:44:58 -03:00
// TODO add support to automatically convert the image to a supported format
2015-09-29 11:27:00 -03:00
switch (fop->m_document->sprite()->pixelFormat()) {
2007-09-18 23:57:02 +00:00
case IMAGE_RGB:
2015-09-29 11:27:00 -03:00
if (!(fop->m_format->support(FILE_SUPPORT_RGB))) {
warnings += "<<- RGB format";
fatal = true;
2007-09-18 23:57:02 +00:00
}
2015-09-29 11:27:00 -03:00
if (!(fop->m_format->support(FILE_SUPPORT_RGBA)) &&
fop->m_document->sprite()->needAlpha()) {
warnings += "<<- Alpha channel";
2007-09-18 23:57:02 +00:00
}
break;
case IMAGE_GRAYSCALE:
2015-09-29 11:27:00 -03:00
if (!(fop->m_format->support(FILE_SUPPORT_GRAY))) {
warnings += "<<- Grayscale format";
fatal = true;
2007-09-18 23:57:02 +00:00
}
2015-09-29 11:27:00 -03:00
if (!(fop->m_format->support(FILE_SUPPORT_GRAYA)) &&
fop->m_document->sprite()->needAlpha()) {
warnings += "<<- Alpha channel";
2007-09-18 23:57:02 +00:00
}
break;
case IMAGE_INDEXED:
2015-09-29 11:27:00 -03:00
if (!(fop->m_format->support(FILE_SUPPORT_INDEXED))) {
warnings += "<<- Indexed format";
fatal = true;
2007-09-18 23:57:02 +00:00
}
break;
}
// Frames support
if (fop->m_roi.frames() > 1) {
2015-09-29 11:27:00 -03:00
if (!fop->m_format->support(FILE_SUPPORT_FRAMES) &&
!fop->m_format->support(FILE_SUPPORT_SEQUENCES)) {
warnings += "<<- Frames";
2011-01-16 17:27:18 -03:00
}
2007-09-18 23:57:02 +00:00
}
// Layers support
2016-06-07 19:38:56 -03:00
if (fop->m_document->sprite()->root()->layersCount() > 1) {
2015-09-29 11:27:00 -03:00
if (!(fop->m_format->support(FILE_SUPPORT_LAYERS))) {
warnings += "<<- Layers";
2007-09-18 23:57:02 +00:00
}
}
// Palettes support
2015-09-29 11:27:00 -03:00
if (fop->m_document->sprite()->getPalettes().size() > 1) {
if (!fop->m_format->support(FILE_SUPPORT_PALETTES) &&
!fop->m_format->support(FILE_SUPPORT_SEQUENCES)) {
warnings += "<<- Palette changes between frames";
2007-09-18 23:57:02 +00:00
}
}
// Check frames support
2015-09-29 11:27:00 -03:00
if (!fop->m_document->sprite()->frameTags().empty()) {
if (!fop->m_format->support(FILE_SUPPORT_FRAME_TAGS)) {
warnings += "<<- Frame tags";
}
}
// Big palettes
2015-09-29 11:27:00 -03:00
if (!fop->m_format->support(FILE_SUPPORT_BIG_PALETTES)) {
for (const Palette* pal : fop->m_document->sprite()->getPalettes()) {
if (pal->size() > 256) {
warnings += "<<- Palettes with more than 256 colors";
break;
}
}
}
// Palette with alpha
2015-09-29 11:27:00 -03:00
if (!fop->m_format->support(FILE_SUPPORT_PALETTE_WITH_ALPHA)) {
bool done = false;
2015-09-29 11:27:00 -03:00
for (const Palette* pal : fop->m_document->sprite()->getPalettes()) {
for (int c=0; c<pal->size(); ++c) {
if (rgba_geta(pal->getEntry(c)) < 255) {
warnings += "<<- Palette with alpha channel";
done = true;
break;
}
}
if (done)
break;
}
}
// Show the confirmation alert
if (!warnings.empty()) {
// Interative
if (context && context->isUIAvailable()) {
warnings += "<<You can use \".ase\" format to keep all this information.";
std::string title, buttons;
if (fatal) {
title = "Error";
buttons = "&Close";
}
else {
title = "Warning";
buttons = "&Yes||&No";
}
int ret = ui::Alert::show("%s<<File format \".%s\" doesn't support:%s"
"<<Do you want continue with \".%s\" anyway?"
"||%s",
title.c_str(),
2015-09-29 11:27:00 -03:00
fop->m_format->name(),
warnings.c_str(),
2015-09-29 11:27:00 -03:00
fop->m_format->name(),
buttons.c_str());
// Operation can't be done (by fatal error) or the user cancel
// the operation
2015-09-29 11:27:00 -03:00
if ((fatal) || (ret != 1))
return nullptr;
}
// No interactive & fatal error?
else if (fatal) {
2015-09-29 11:27:00 -03:00
fop->setError(warnings.c_str());
return fop.release();
2007-09-18 23:57:02 +00:00
}
}
2011-01-16 17:27:18 -03:00
// Use the "sequence" interface.
2015-09-29 11:27:00 -03:00
if (fop->m_format->support(FILE_SUPPORT_SEQUENCES)) {
fop->prepareForSequence();
std::string fn = filename;
2016-05-31 16:53:30 -03:00
std::string fn_format = filenameFormatArg;
if (fn_format.empty()) {
fn_format = get_default_filename_format(
fn,
true, // With path
(fop->m_roi.frames() > 1), // Has frames
false, // Doesn't have layers
false); // Doesn't have tags
}
Sprite* spr = fop->m_document->sprite();
frame_t outputFrame = 0;
for (frame_t frame : fop->m_roi.selectedFrames()) {
FrameTag* innerTag = (fop->m_roi.frameTag() ? fop->m_roi.frameTag(): spr->frameTags().innerTag(frame));
FrameTag* outerTag = (fop->m_roi.frameTag() ? fop->m_roi.frameTag(): spr->frameTags().outerTag(frame));
FilenameInfo fnInfo;
fnInfo
.filename(fn)
.sliceName(fop->m_roi.slice() ? fop->m_roi.slice()->name(): "")
.innerTagName(innerTag ? innerTag->name(): "")
.outerTagName(outerTag ? outerTag->name(): "")
.frame(outputFrame)
.tagFrame(innerTag ? frame - innerTag->fromFrame():
outputFrame);
fop->m_seq.filename_list.push_back(
filename_formatter(fn_format, fnInfo));
++outputFrame;
}
if (context && context->isUIAvailable() &&
fop->m_seq.filename_list.size() > 1 &&
ui::Alert::show("Notice"
"<<Do you want to export the animation in %d files?"
"<<%s, %s..."
"||&Agree||&Cancel",
int(fop->m_seq.filename_list.size()),
base::get_file_name(fop->m_seq.filename_list[0]).c_str(),
base::get_file_name(fop->m_seq.filename_list[1]).c_str()) != 1) {
return nullptr;
}
}
else
2015-09-29 11:27:00 -03:00
fop->m_filename = filename;
// Configure output format?
2015-09-29 11:27:00 -03:00
if (fop->m_format->support(FILE_SUPPORT_GET_FORMAT_OPTIONS)) {
base::SharedPtr<FormatOptions> opts =
2015-09-29 11:27:00 -03:00
fop->m_format->getFormatOptions(fop);
2011-01-16 17:27:18 -03:00
// Does the user cancelled the operation?
if (!opts)
2015-09-29 11:27:00 -03:00
return nullptr;
fop->m_formatOptions = opts;
fop->m_document->setFormatOptions(opts);
}
// Does data file exist?
std::string dataFilename = base::replace_extension(filename, "aseprite-data");
if (base::is_file(dataFilename))
fop->m_dataFilename = dataFilename;
2015-09-29 11:27:00 -03:00
return fop.release();
}
// Executes the file operation: loads or saves the sprite.
//
// It can be called from a different thread of the one used
2015-09-29 11:27:00 -03:00
// by FileOp::createLoadDocumentOperation() or createSaveDocumentOperation().
//
2015-09-29 11:27:00 -03:00
// After this function you must to mark the FileOp as "done" calling
// FileOp::done() function.
//
// TODO refactor this code
2015-09-29 11:27:00 -03:00
void FileOp::operate(IFileOpProgress* progress)
{
2015-09-29 11:27:00 -03:00
ASSERT(!isDone());
2015-09-29 11:27:00 -03:00
m_progressInterface = progress;
// Load //////////////////////////////////////////////////////////////////////
2015-09-29 11:27:00 -03:00
if (m_type == FileOpLoad &&
m_format != NULL &&
m_format->support(FILE_SUPPORT_LOAD)) {
// Load a sequence
2015-09-29 11:27:00 -03:00
if (isSequence()) {
// Default palette
2015-09-29 11:27:00 -03:00
m_seq.palette->makeBlack();
// Load the sequence
2015-09-29 11:27:00 -03:00
frame_t frames(m_seq.filename_list.size());
frame_t frame(0);
Image* old_image = nullptr;
// TODO setPalette for each frame???
auto add_image = [&]() {
2015-09-29 11:27:00 -03:00
m_seq.last_cel->data()->setImage(m_seq.image);
m_seq.layer->addCel(m_seq.last_cel);
2015-09-29 11:27:00 -03:00
if (m_document->sprite()->palette(frame)
->countDiff(m_seq.palette, NULL, NULL) > 0) {
m_seq.palette->setFrame(frame);
m_document->sprite()->setPalette(m_seq.palette, true);
}
2015-09-29 11:27:00 -03:00
old_image = m_seq.image.get();
m_seq.image.reset(NULL);
m_seq.last_cel = NULL;
};
2015-09-29 11:27:00 -03:00
m_seq.has_alpha = false;
m_seq.progress_offset = 0.0f;
m_seq.progress_fraction = 1.0f / (double)frames;
2015-09-29 11:27:00 -03:00
auto it = m_seq.filename_list.begin(),
end = m_seq.filename_list.end();
for (; it != end; ++it) {
2015-09-29 11:27:00 -03:00
m_filename = it->c_str();
// Call the "load" procedure to read the first bitmap.
2015-09-29 11:27:00 -03:00
bool loadres = m_format->load(this);
if (!loadres) {
2015-09-29 11:27:00 -03:00
setError("Error loading frame %d from file \"%s\"\n",
frame+1, m_filename.c_str());
}
// For the first frame...
if (!old_image) {
// Error reading the first frame
2015-09-29 11:27:00 -03:00
if (!loadres || !m_document || !m_seq.last_cel) {
m_seq.image.reset();
delete m_seq.last_cel;
delete m_document;
m_document = nullptr;
break;
}
// Read ok
else {
2013-03-30 19:53:52 -03:00
// Add the keyframe
add_image();
}
}
2013-03-30 19:53:52 -03:00
// For other frames
else {
2013-03-30 19:53:52 -03:00
// All done (or maybe not enough memory)
2015-09-29 11:27:00 -03:00
if (!loadres || !m_seq.last_cel) {
m_seq.image.reset();
delete m_seq.last_cel;
break;
}
2013-03-30 19:53:52 -03:00
// Compare the old frame with the new one
#if USE_LINK // TODO this should be configurable through a check-box
2015-09-29 11:27:00 -03:00
if (count_diff_between_images(old_image, m_seq.image)) {
add_image();
}
2013-03-30 19:53:52 -03:00
// We don't need this image
else {
2015-09-29 11:27:00 -03:00
delete m_seq.image;
2013-03-30 19:53:52 -03:00
// But add a link frame
2015-09-29 11:27:00 -03:00
m_seq.last_cel->image = image_index;
layer_add_frame(m_seq.layer, m_seq.last_cel);
2015-09-29 11:27:00 -03:00
m_seq.last_image = NULL;
m_seq.last_cel = NULL;
}
#else
add_image();
#endif
}
2007-09-18 23:57:02 +00:00
2012-07-08 21:09:09 -03:00
++frame;
2015-09-29 11:27:00 -03:00
m_seq.progress_offset += m_seq.progress_fraction;
}
2015-09-29 11:27:00 -03:00
m_filename = *m_seq.filename_list.begin();
2007-09-18 23:57:02 +00:00
// Final setup
2015-09-29 11:27:00 -03:00
if (m_document != NULL) {
// Configure the layer as the 'Background'
2015-09-29 11:27:00 -03:00
if (!m_seq.has_alpha)
m_seq.layer->configureAsBackground();
// Set the frames range
2015-09-29 11:27:00 -03:00
m_document->sprite()->setTotalFrames(frame);
// Sets special options from the specific format (e.g. BMP
// file can contain the number of bits per pixel).
m_document->setFormatOptions(m_formatOptions);
}
}
// Direct load from one file.
else {
// Call the "load" procedure.
if (!m_format->load(this)) {
2015-09-29 11:27:00 -03:00
setError("Error loading sprite from file \"%s\"\n",
m_filename.c_str());
}
}
// Load special data from .aseprite-data file
if (m_document &&
m_document->sprite() &&
!m_dataFilename.empty()) {
try {
load_aseprite_data_file(m_dataFilename, m_document);
}
catch (const std::exception& ex) {
setError("Error loading data file: %s\n", ex.what());
}
}
}
// Save //////////////////////////////////////////////////////////////////////
2015-09-29 11:27:00 -03:00
else if (m_type == FileOpSave &&
m_format != NULL &&
m_format->support(FILE_SUPPORT_SAVE)) {
#ifdef ENABLE_SAVE
// Save a sequence
2015-09-29 11:27:00 -03:00
if (isSequence()) {
ASSERT(m_format->support(FILE_SUPPORT_SEQUENCES));
2015-09-29 11:27:00 -03:00
Sprite* sprite = m_document->sprite();
// Create a temporary bitmap
2015-09-29 11:27:00 -03:00
m_seq.image.reset(Image::create(sprite->pixelFormat(),
sprite->width(),
sprite->height()));
2015-09-29 11:27:00 -03:00
m_seq.progress_offset = 0.0f;
m_seq.progress_fraction = 1.0f / (double)sprite->totalFrames();
// For each frame in the sprite.
render::Render render;
frame_t outputFrame = 0;
for (frame_t frame : m_roi.selectedFrames()) {
2015-09-29 11:27:00 -03:00
// Draw the "frame" in "m_seq.image"
if (m_roi.slice()) {
const SliceKey* key = m_roi.slice()->getByFrame(frame);
if (!key || key->isEmpty())
continue; // Skip frame because there is no slice key
m_seq.image.reset(
Image::create(sprite->pixelFormat(),
key->bounds().w,
key->bounds().h));
render.renderSprite(
m_seq.image.get(), sprite, frame,
gfx::Clip(gfx::Point(0, 0), key->bounds()));
}
else {
render.renderSprite(m_seq.image.get(), sprite, frame);
}
// Setup the palette.
2015-09-29 11:27:00 -03:00
sprite->palette(frame)->copyColorsTo(m_seq.palette);
2007-09-18 23:57:02 +00:00
// Setup the filename to be used.
m_filename = m_seq.filename_list[outputFrame];
// Call the "save" procedure... did it fail?
2015-09-29 11:27:00 -03:00
if (!m_format->save(this)) {
setError("Error saving frame %d in the file \"%s\"\n",
outputFrame+1, m_filename.c_str());
break;
}
2007-09-18 23:57:02 +00:00
2015-09-29 11:27:00 -03:00
m_seq.progress_offset += m_seq.progress_fraction;
++outputFrame;
}
2015-09-29 11:27:00 -03:00
m_filename = *m_seq.filename_list.begin();
m_document->setFilename(m_filename);
// Destroy the image
2015-09-29 11:27:00 -03:00
m_seq.image.reset(NULL);
2007-09-18 23:57:02 +00:00
}
// Direct save to a file.
2007-09-18 23:57:02 +00:00
else {
// Call the "save" procedure.
if (!m_format->save(this)) {
2015-09-29 11:27:00 -03:00
setError("Error saving the sprite in the file \"%s\"\n",
m_filename.c_str());
}
}
// Save special data from .aseprite-data file
if (m_document &&
m_document->sprite() &&
!hasError() &&
!m_dataFilename.empty()) {
try {
save_aseprite_data_file(m_dataFilename, m_document);
}
catch (const std::exception& ex) {
setError("Error loading data file: %s\n", ex.what());
}
2007-09-18 23:57:02 +00:00
}
#else
2015-09-29 11:27:00 -03:00
setError(
"Save operation is not supported in trial version.\n"
"Go to " WEBSITE_DOWNLOAD " and get the full-version.");
#endif
}
// Progress = 100%
2015-09-29 11:27:00 -03:00
setProgress(1.0f);
}
// After mark the 'fop' as 'done' you must to free it calling fop_free().
2015-09-29 11:27:00 -03:00
void FileOp::done()
{
// Finally done.
2015-09-29 11:27:00 -03:00
scoped_lock lock(m_mutex);
m_done = true;
}
2015-09-29 11:27:00 -03:00
void FileOp::stop()
{
2015-09-29 11:27:00 -03:00
scoped_lock lock(m_mutex);
if (!m_done)
m_stop = true;
}
FileOp::~FileOp()
{
2015-09-29 11:27:00 -03:00
if (m_format)
m_format->destroyData(this);
2015-09-29 11:27:00 -03:00
delete m_seq.palette;
}
void FileOp::createDocument(Sprite* spr)
{
// spr can be NULL if the sprite is set in onPostLoad() then
2015-09-29 11:27:00 -03:00
ASSERT(m_document == NULL);
m_document = new Document(spr);
}
2015-09-29 11:27:00 -03:00
void FileOp::postLoad()
{
2015-09-29 11:27:00 -03:00
if (m_document == NULL)
return;
// Set the filename.
2016-04-11 19:17:39 -03:00
std::string fn;
2015-09-29 11:27:00 -03:00
if (isSequence())
2016-04-11 19:17:39 -03:00
fn = m_seq.filename_list.begin()->c_str();
else
2016-04-11 19:17:39 -03:00
fn = m_filename.c_str();
m_document->setFilename(fn);
2015-09-29 11:27:00 -03:00
bool result = m_format->postLoad(this);
if (!result) {
// Destroy the document
2015-09-29 11:27:00 -03:00
delete m_document;
m_document = nullptr;
return;
}
2015-09-29 11:27:00 -03:00
Sprite* sprite = m_document->sprite();
if (sprite) {
// Creates a suitable palette for RGB images
if (sprite->pixelFormat() == IMAGE_RGB &&
sprite->getPalettes().size() <= 1 &&
sprite->palette(frame_t(0))->isBlack()) {
base::SharedPtr<Palette> palette(
render::create_palette_from_sprite(
sprite, frame_t(0), sprite->lastFrame(), true,
nullptr, nullptr));
sprite->resetPalettes();
sprite->setPalette(palette.get(), false);
}
}
2015-09-29 11:27:00 -03:00
m_document->markAsSaved();
}
base::SharedPtr<FormatOptions> FileOp::formatOptions() const
2015-09-29 11:27:00 -03:00
{
return m_formatOptions;
}
void FileOp::setFormatOptions(const base::SharedPtr<FormatOptions>& opts)
{
ASSERT(!m_formatOptions);
m_formatOptions = opts;
}
2015-09-29 11:27:00 -03:00
void FileOp::sequenceSetNColors(int ncolors)
{
2015-09-29 11:27:00 -03:00
m_seq.palette->resize(ncolors);
}
2015-09-29 11:27:00 -03:00
int FileOp::sequenceGetNColors() const
{
2015-09-29 11:27:00 -03:00
return m_seq.palette->size();
}
2015-09-29 11:27:00 -03:00
void FileOp::sequenceSetColor(int index, int r, int g, int b)
{
2015-09-29 11:27:00 -03:00
m_seq.palette->setEntry(index, rgba(r, g, b, 255));
}
2015-09-29 11:27:00 -03:00
void FileOp::sequenceGetColor(int index, int* r, int* g, int* b) const
{
uint32_t c;
ASSERT(index >= 0);
2015-09-29 11:27:00 -03:00
if (index >= 0 && index < m_seq.palette->size())
c = m_seq.palette->getEntry(index);
else
c = rgba(0, 0, 0, 255); // Black color
*r = rgba_getr(c);
*g = rgba_getg(c);
*b = rgba_getb(c);
}
2015-09-29 11:27:00 -03:00
void FileOp::sequenceSetAlpha(int index, int a)
{
2015-09-29 11:27:00 -03:00
int c = m_seq.palette->getEntry(index);
int r = rgba_getr(c);
int g = rgba_getg(c);
int b = rgba_getb(c);
2015-09-29 11:27:00 -03:00
m_seq.palette->setEntry(index, rgba(r, g, b, a));
}
2015-09-29 11:27:00 -03:00
void FileOp::sequenceGetAlpha(int index, int* a) const
{
ASSERT(index >= 0);
2015-09-29 11:27:00 -03:00
if (index >= 0 && index < m_seq.palette->size())
*a = rgba_geta(m_seq.palette->getEntry(index));
else
*a = 0;
}
2015-09-29 11:27:00 -03:00
Image* FileOp::sequenceImage(PixelFormat pixelFormat, int w, int h)
{
Sprite* sprite;
// Create the image
2015-09-29 11:27:00 -03:00
if (!m_document) {
sprite = new Sprite(pixelFormat, w, h, 256);
try {
LayerImage* layer = new LayerImage(sprite);
// Add the layer
2016-06-07 19:38:56 -03:00
sprite->root()->addLayer(layer);
// Done
2015-09-29 11:27:00 -03:00
createDocument(sprite);
m_seq.layer = layer;
}
catch (...) {
delete sprite;
throw;
}
2007-09-18 23:57:02 +00:00
}
else {
2015-09-29 11:27:00 -03:00
sprite = m_document->sprite();
if (sprite->pixelFormat() != pixelFormat)
2015-09-29 11:27:00 -03:00
return nullptr;
2007-09-18 23:57:02 +00:00
}
2015-09-29 11:27:00 -03:00
if (m_seq.last_cel) {
setError("Error: called two times FileOp::sequenceImage()\n");
2015-09-29 11:27:00 -03:00
return nullptr;
}
// Create a bitmap
2015-09-29 11:27:00 -03:00
m_seq.image.reset(Image::create(pixelFormat, w, h));
m_seq.last_cel = new Cel(m_seq.frame++, ImageRef(nullptr));
2015-09-29 11:27:00 -03:00
return m_seq.image.get();
}
2015-09-29 11:27:00 -03:00
void FileOp::setError(const char *format, ...)
{
char buf_error[4096]; // TODO possible stack overflow
va_list ap;
va_start(ap, format);
vsnprintf(buf_error, sizeof(buf_error), format, ap);
va_end(ap);
// Concatenate the new error
{
2015-09-29 11:27:00 -03:00
scoped_lock lock(m_mutex);
m_error += buf_error;
}
}
2015-09-29 11:27:00 -03:00
void FileOp::setProgress(double progress)
{
2015-09-29 11:27:00 -03:00
scoped_lock lock(m_mutex);
2015-09-29 11:27:00 -03:00
if (isSequence()) {
m_progress =
m_seq.progress_offset +
m_seq.progress_fraction*progress;
}
else {
2015-09-29 11:27:00 -03:00
m_progress = progress;
}
2015-09-29 11:27:00 -03:00
if (m_progressInterface)
m_progressInterface->ackFileOpProgress(progress);
}
void FileOp::getFilenameList(std::vector<std::string>& output) const
{
if (isSequence()) {
output = m_seq.filename_list;
}
else {
output.push_back(m_filename);
}
}
2015-09-29 11:27:00 -03:00
double FileOp::progress() const
{
double progress;
{
2015-09-29 11:27:00 -03:00
scoped_lock lock(m_mutex);
progress = m_progress;
}
return progress;
}
2015-09-29 11:27:00 -03:00
// Returns true when the file operation has finished, this means, when
// the FileOp::operate() routine ends.
bool FileOp::isDone() const
{
bool done;
{
2015-09-29 11:27:00 -03:00
scoped_lock lock(m_mutex);
done = m_done;
}
return done;
}
2015-09-29 11:27:00 -03:00
bool FileOp::isStop() const
{
bool stop;
{
2015-09-29 11:27:00 -03:00
scoped_lock lock(m_mutex);
stop = m_stop;
}
return stop;
}
2015-09-29 11:27:00 -03:00
FileOp::FileOp(FileOpType type, Context* context)
: m_type(type)
, m_format(nullptr)
, m_context(context)
, m_document(nullptr)
, m_progress(0.0)
, m_progressInterface(nullptr)
, m_done(false)
, m_stop(false)
, m_oneframe(false)
{
2015-09-29 11:27:00 -03:00
m_seq.palette = nullptr;
m_seq.image.reset(nullptr);
m_seq.progress_offset = 0.0f;
m_seq.progress_fraction = 0.0f;
m_seq.frame = frame_t(0);
m_seq.layer = nullptr;
m_seq.last_cel = nullptr;
m_seq.flags = 0;
}
2015-09-29 11:27:00 -03:00
void FileOp::prepareForSequence()
{
2015-09-29 11:27:00 -03:00
m_seq.palette = new Palette(frame_t(0), 256);
m_formatOptions.reset();
2007-09-18 23:57:02 +00:00
}
} // namespace app