Merge branch 'refactor-cli' into beta

This commit is contained in:
David Capello 2016-06-06 17:57:37 -03:00
commit f18b1dfdb7
39 changed files with 1904 additions and 874 deletions

View File

@ -172,6 +172,7 @@ if(ENABLE_TESTS)
find_tests(render render-lib)
find_tests(css css-lib)
find_tests(ui ui-lib)
find_tests(app/cli app-lib)
find_tests(app/file app-lib)
find_tests(app app-lib)
find_tests(. app-lib)

View File

@ -92,9 +92,13 @@ add_library(app-lib
app.cpp
app_brushes.cpp
app_menus.cpp
app_options.cpp
app_render.cpp
check_update.cpp
cli/app_options.cpp
cli/cli_open_file.cpp
cli/cli_processor.cpp
cli/default_cli_delegate.cpp
cli/preview_cli_delegate.cpp
cmd.cpp
cmd/add_cel.cpp
cmd/add_frame.cpp

View File

@ -11,21 +11,18 @@
#include "app/app.h"
#include "app/app_options.h"
#include "app/check_update.h"
#include "app/cli/app_options.h"
#include "app/cli/cli_processor.h"
#include "app/cli/default_cli_delegate.h"
#include "app/cli/preview_cli_delegate.h"
#include "app/color_utils.h"
#include "app/commands/cmd_save_file.h"
#include "app/commands/cmd_sprite_size.h"
#include "app/commands/commands.h"
#include "app/commands/params.h"
#include "app/console.h"
#include "app/crash/data_recovery.h"
#include "app/document_exporter.h"
#include "app/document_undo.h"
#include "app/file/file.h"
#include "app/file/file_formats_manager.h"
#include "app/file_system.h"
#include "app/filename_formatter.h"
#include "app/gui_xml.h"
#include "app/ini_file.h"
#include "app/log.h"
@ -54,21 +51,12 @@
#include "app/ui_context.h"
#include "app/util/clipboard.h"
#include "app/webserver.h"
#include "base/convert_to.h"
#include "base/exception.h"
#include "base/fs.h"
#include "base/path.h"
#include "base/split_string.h"
#include "base/unique_ptr.h"
#include "doc/document_observer.h"
#include "doc/frame_tag.h"
#include "doc/image.h"
#include "doc/layer.h"
#include "doc/layers_range.h"
#include "doc/palette.h"
#include "doc/site.h"
#include "doc/sprite.h"
#include "render/render.h"
#include "script/engine_delegate.h"
#include "she/display.h"
#include "she/error.h"
@ -134,13 +122,6 @@ public:
};
class StdoutEngineDelegate : public script::EngineDelegate {
public:
void onConsolePrint(const char* text) override {
printf("%s\n", text);
}
};
App* App::m_instance = NULL;
App::App()
@ -149,7 +130,6 @@ App::App()
, m_legacy(NULL)
, m_isGui(false)
, m_isShell(false)
, m_exporter(NULL)
{
ASSERT(m_instance == NULL);
m_instance = this;
@ -157,7 +137,7 @@ App::App()
void App::initialize(const AppOptions& options)
{
m_isGui = options.startUI();
m_isGui = options.startUI() && !options.previewCLI();
m_isShell = options.startShell();
if (m_isGui)
m_uiSystem.reset(new ui::UISystem);
@ -182,16 +162,10 @@ void App::initialize(const AppOptions& options)
m_legacy = new LegacyModules(isGui() ? REQUIRE_INTERFACE: 0);
m_brushes.reset(new AppBrushes);
if (options.hasExporterParams())
m_exporter.reset(new DocumentExporter);
// Data recovery is enabled only in GUI mode
if (isGui() && preferences().general.dataRecovery())
m_modules->createDataRecovery();
// Register well-known image file types.
FileFormatsManager::instance()->registerAllFormats();
if (isPortable())
LOG("Running in portable mode\n");
@ -200,7 +174,6 @@ void App::initialize(const AppOptions& options)
load_default_palette(options.paletteFileName());
// Initialize GUI interface
UIContext* ctx = UIContext::instance();
if (isGui()) {
LOG("GUI mode\n");
@ -232,427 +205,15 @@ void App::initialize(const AppOptions& options)
// Procress options
LOG("Processing options...\n");
{
base::UniquePtr<CliDelegate> delegate;
if (options.previewCLI())
delegate.reset(new PreviewCliDelegate);
else
delegate.reset(new DefaultCliDelegate);
bool ignoreEmpty = false;
bool trim = false;
Params cropParams;
SpriteSheetType sheetType = SpriteSheetType::None;
// Open file specified in the command line
if (!options.values().empty()) {
Console console;
bool splitLayers = false;
bool splitLayersSaveAs = false;
bool allLayers = false;
bool listLayers = false;
bool listTags = false;
std::string importLayer;
std::string importLayerSaveAs;
std::string filenameFormat;
std::string frameTagName;
std::string frameRange;
for (const auto& value : options.values()) {
const AppOptions::Option* opt = value.option();
// Special options/commands
if (opt) {
// --data <file.json>
if (opt == &options.data()) {
if (m_exporter)
m_exporter->setDataFilename(value.value());
}
// --format <format>
else if (opt == &options.format()) {
if (m_exporter) {
DocumentExporter::DataFormat format = DocumentExporter::DefaultDataFormat;
if (value.value() == "json-hash")
format = DocumentExporter::JsonHashDataFormat;
else if (value.value() == "json-array")
format = DocumentExporter::JsonArrayDataFormat;
m_exporter->setDataFormat(format);
}
}
// --sheet <file.png>
else if (opt == &options.sheet()) {
if (m_exporter)
m_exporter->setTextureFilename(value.value());
}
// --sheet-width <width>
else if (opt == &options.sheetWidth()) {
if (m_exporter)
m_exporter->setTextureWidth(strtol(value.value().c_str(), NULL, 0));
}
// --sheet-height <height>
else if (opt == &options.sheetHeight()) {
if (m_exporter)
m_exporter->setTextureHeight(strtol(value.value().c_str(), NULL, 0));
}
// --sheet-pack
else if (opt == &options.sheetType()) {
if (value.value() == "horizontal")
sheetType = SpriteSheetType::Horizontal;
else if (value.value() == "vertical")
sheetType = SpriteSheetType::Vertical;
else if (value.value() == "rows")
sheetType = SpriteSheetType::Rows;
else if (value.value() == "columns")
sheetType = SpriteSheetType::Columns;
else if (value.value() == "packed")
sheetType = SpriteSheetType::Packed;
}
// --sheet-pack
else if (opt == &options.sheetPack()) {
sheetType = SpriteSheetType::Packed;
}
// --split-layers
else if (opt == &options.splitLayers()) {
splitLayers = true;
splitLayersSaveAs = true;
}
// --layer <layer-name>
else if (opt == &options.layer()) {
importLayer = value.value();
importLayerSaveAs = value.value();
}
// --all-layers
else if (opt == &options.allLayers()) {
allLayers = true;
}
// --frame-tag <tag-name>
else if (opt == &options.frameTag()) {
frameTagName = value.value();
}
// --frame-range from,to
else if (opt == &options.frameRange()) {
frameRange = value.value();
}
// --ignore-empty
else if (opt == &options.ignoreEmpty()) {
ignoreEmpty = true;
}
// --border-padding
else if (opt == &options.borderPadding()) {
if (m_exporter)
m_exporter->setBorderPadding(strtol(value.value().c_str(), NULL, 0));
}
// --shape-padding
else if (opt == &options.shapePadding()) {
if (m_exporter)
m_exporter->setShapePadding(strtol(value.value().c_str(), NULL, 0));
}
// --inner-padding
else if (opt == &options.innerPadding()) {
if (m_exporter)
m_exporter->setInnerPadding(strtol(value.value().c_str(), NULL, 0));
}
// --trim
else if (opt == &options.trim()) {
trim = true;
}
// --crop x,y,width,height
else if (opt == &options.crop()) {
std::vector<std::string> parts;
base::split_string(value.value(), parts, ",");
if (parts.size() < 4)
throw std::runtime_error("--crop needs four parameters separated by comma (,)\n"
"Usage: --crop x,y,width,height\n"
"E.g. --crop 0,0,32,32");
cropParams.set("x", parts[0].c_str());
cropParams.set("y", parts[1].c_str());
cropParams.set("width", parts[2].c_str());
cropParams.set("height", parts[3].c_str());
}
// --filename-format
else if (opt == &options.filenameFormat()) {
filenameFormat = value.value();
}
// --save-as <filename>
else if (opt == &options.saveAs()) {
Document* doc = NULL;
if (!ctx->documents().empty())
doc = dynamic_cast<Document*>(ctx->documents().lastAdded());
if (!doc) {
console.printf("A document is needed before --save-as argument\n");
}
else {
ctx->setActiveDocument(doc);
std::string format = filenameFormat;
Command* saveAsCommand = CommandsModule::instance()->getCommandByName(CommandId::SaveFileCopyAs);
Command* trimCommand = CommandsModule::instance()->getCommandByName(CommandId::AutocropSprite);
Command* cropCommand = CommandsModule::instance()->getCommandByName(CommandId::CropSprite);
Command* undoCommand = CommandsModule::instance()->getCommandByName(CommandId::Undo);
// --save-as with --split-layers
if (splitLayersSaveAs) {
std::string fn, fmt;
if (format.empty()) {
if (doc->sprite()->totalFrames() > frame_t(1))
format = "{path}/{title} ({layer}) {frame}.{extension}";
else
format = "{path}/{title} ({layer}).{extension}";
}
// Store in "visibility" the original "visible" state of every layer.
std::vector<bool> visibility(doc->sprite()->countLayers());
int i = 0;
for (Layer* layer : doc->sprite()->layers())
visibility[i++] = layer->isVisible();
// For each layer, hide other ones and save the sprite.
i = 0;
for (Layer* show : doc->sprite()->layers()) {
// If the user doesn't want all layers and this one is hidden.
if (!visibility[i++])
continue; // Just ignore this layer.
// Make this layer ("show") the only one visible.
for (Layer* hide : doc->sprite()->layers())
hide->setVisible(hide == show);
FilenameInfo fnInfo;
fnInfo
.filename(value.value())
.layerName(show->name());
fn = filename_formatter(format, fnInfo);
fmt = filename_formatter(format, fnInfo, false);
if (!cropParams.empty())
ctx->executeCommand(cropCommand, cropParams);
// TODO --trim command with --save-as doesn't make too
// much sense as we lost the trim rectangle
// information (e.g. we don't have sheet .json) Also,
// we should trim each frame individually (a process
// that can be done only in fop_operate()).
if (trim)
ctx->executeCommand(trimCommand);
Params params;
params.set("filename", fn.c_str());
params.set("filename-format", fmt.c_str());
ctx->executeCommand(saveAsCommand, params);
if (trim) { // Undo trim command
ctx->executeCommand(undoCommand);
// Just in case allow non-linear history is enabled
// we clear redo information
doc->undoHistory()->clearRedo();
}
}
// Restore layer visibility
i = 0;
for (Layer* layer : doc->sprite()->layers())
layer->setVisible(visibility[i++]);
}
else {
// Show only one layer
if (!importLayerSaveAs.empty()) {
for (Layer* layer : doc->sprite()->layers())
layer->setVisible(layer->name() == importLayerSaveAs);
}
if (!cropParams.empty())
ctx->executeCommand(cropCommand, cropParams);
if (trim)
ctx->executeCommand(trimCommand);
Params params;
params.set("filename", value.value().c_str());
params.set("filename-format", format.c_str());
ctx->executeCommand(saveAsCommand, params);
if (trim) { // Undo trim command
ctx->executeCommand(undoCommand);
// Just in case allow non-linear history is enabled
// we clear redo information
doc->undoHistory()->clearRedo();
}
}
}
}
// --scale <factor>
else if (opt == &options.scale()) {
Command* command = CommandsModule::instance()->getCommandByName(CommandId::SpriteSize);
double scale = strtod(value.value().c_str(), NULL);
static_cast<SpriteSizeCommand*>(command)->setScale(scale, scale);
// Scale all sprites
for (auto doc : ctx->documents()) {
ctx->setActiveDocument(static_cast<app::Document*>(doc));
ctx->executeCommand(command);
}
}
// --shrink-to <width,height>
else if (opt == &options.shrinkTo()) {
std::vector<std::string> dimensions;
base::split_string(value.value(), dimensions, ",");
if (dimensions.size() < 2)
throw std::runtime_error("--shrink-to needs two parameters separated by comma (,)\n"
"Usage: --shrink-to width,height\n"
"E.g. --shrink-to 128,64");
double maxWidth = base::convert_to<double>(dimensions[0]);
double maxHeight = base::convert_to<double>(dimensions[1]);
double scaleWidth, scaleHeight, scale;
// Shrink all sprites if needed
for (auto doc : ctx->documents()) {
ctx->setActiveDocument(static_cast<app::Document*>(doc));
scaleWidth = (doc->width() > maxWidth ? maxWidth / doc->width() : 1.0);
scaleHeight = (doc->height() > maxHeight ? maxHeight / doc->height() : 1.0);
if (scaleWidth < 1.0 || scaleHeight < 1.0) {
scale = MIN(scaleWidth, scaleHeight);
Command* command = CommandsModule::instance()->getCommandByName(CommandId::SpriteSize);
static_cast<SpriteSizeCommand*>(command)->setScale(scale, scale);
ctx->executeCommand(command);
}
}
}
// --script <filename>
else if (opt == &options.script()) {
std::string script = value.value();
StdoutEngineDelegate delegate;
AppScripting engine(&delegate);
engine.evalFile(script);
}
// --list-layers
else if (opt == &options.listLayers()) {
listLayers = true;
if (m_exporter)
m_exporter->setListLayers(true);
}
// --list-tags
else if (opt == &options.listTags()) {
listTags = true;
if (m_exporter)
m_exporter->setListFrameTags(true);
}
}
// File names aren't associated to any option
else {
const std::string& filename = base::normalize_path(value.value());
app::Document* oldDoc = ctx->activeDocument();
Command* openCommand = CommandsModule::instance()->getCommandByName(CommandId::OpenFile);
Params params;
params.set("filename", filename.c_str());
ctx->executeCommand(openCommand, params);
app::Document* doc = ctx->activeDocument();
// If the active document is equal to the previous one, it
// means that we couldn't open this specific document.
if (doc == oldDoc)
doc = nullptr;
// List layers and/or tags
if (doc) {
// Show all layers
if (allLayers) {
for (Layer* layer : doc->sprite()->layers())
layer->setVisible(true);
}
if (listLayers) {
listLayers = false;
for (Layer* layer : doc->sprite()->layers()) {
if (layer->isVisible())
std::cout << layer->name() << "\n";
}
}
if (listTags) {
listTags = false;
for (FrameTag* tag : doc->sprite()->frameTags())
std::cout << tag->name() << "\n";
}
if (m_exporter) {
FrameTag* frameTag = nullptr;
if (!frameTagName.empty()) {
frameTag = doc->sprite()->frameTags().getByName(frameTagName);
}
else if (!frameRange.empty()) {
std::vector<std::string> splitRange;
base::split_string(frameRange, splitRange, ",");
if (splitRange.size() < 2)
throw std::runtime_error("--frame-range needs two parameters separated by comma (,)\n"
"Usage: --frame-range from,to\n"
"E.g. --frame-range 0,99");
frameTag = new FrameTag(base::convert_to<frame_t>(splitRange[0]),
base::convert_to<frame_t>(splitRange[1]));
}
if (!importLayer.empty()) {
Layer* foundLayer = NULL;
for (Layer* layer : doc->sprite()->layers()) {
if (layer->name() == importLayer) {
foundLayer = layer;
break;
}
}
if (foundLayer)
m_exporter->addDocument(doc, foundLayer, frameTag);
}
else if (splitLayers) {
for (auto layer : doc->sprite()->layers()) {
if (layer->isVisible())
m_exporter->addDocument(doc, layer, frameTag);
}
}
else {
m_exporter->addDocument(doc, nullptr, frameTag);
}
}
}
if (!importLayer.empty())
importLayer.clear();
if (splitLayers)
splitLayers = false;
if (listLayers)
listLayers = false;
if (listTags)
listTags = false;
}
}
if (m_exporter && !filenameFormat.empty())
m_exporter->setFilenameFormat(filenameFormat);
}
// Export
if (m_exporter) {
LOG("Exporting sheet...\n");
if (sheetType != SpriteSheetType::None)
m_exporter->setSpriteSheetType(sheetType);
if (ignoreEmpty)
m_exporter->setIgnoreEmptyCels(true);
if (trim)
m_exporter->setTrimCels(true);
base::UniquePtr<Document> spriteSheet(m_exporter->exportSheet());
m_exporter.reset(NULL);
LOG("Export sprite sheet: Done\n");
CliProcessor cli(delegate.get(), options);
cli.process();
}
she::instance()->finishLaunching();
@ -689,7 +250,7 @@ void App::run()
// Start shell to execute scripts.
if (m_isShell) {
StdoutEngineDelegate delegate;
script::StdoutEngineDelegate delegate;
AppScripting engine(&delegate);
engine.printLastResult();
Shell shell;

View File

@ -30,7 +30,6 @@ namespace app {
class AppOptions;
class ContextBar;
class Document;
class DocumentExporter;
class INotificationDelegate;
class InputChain;
class LegacyModules;
@ -107,7 +106,6 @@ namespace app {
bool m_isShell;
base::UniquePtr<MainWindow> m_mainWindow;
FileList m_files;
base::UniquePtr<DocumentExporter> m_exporter;
base::UniquePtr<AppBrushes> m_brushes;
};

View File

@ -9,11 +9,10 @@
#include "config.h"
#endif
#include "app/app_options.h"
#include "app/cli/app_options.h"
#include "base/path.h"
#include <cstdlib>
#include <iostream>
namespace app {
@ -24,10 +23,14 @@ AppOptions::AppOptions(int argc, const char* argv[])
: m_exeName(base::get_file_name(argv[0]))
, m_startUI(true)
, m_startShell(false)
, m_previewCLI(false)
, m_showHelp(false)
, m_showVersion(false)
, m_verboseLevel(kNoVerbose)
, m_palette(m_po.add("palette").requiresValue("<filename>").description("Use a specific palette by default"))
, m_shell(m_po.add("shell").description("Start an interactive console to execute scripts"))
, m_batch(m_po.add("batch").mnemonic('b').description("Do not start the UI"))
, m_preview(m_po.add("preview").mnemonic('p').description("Do not execute actions, just print what will be\ndone"))
, m_saveAs(m_po.add("save-as").requiresValue("<filename>").description("Save the last given document with other format"))
, m_scale(m_po.add("scale").requiresValue("<factor>").description("Resize all previous opened documents"))
, m_shrinkTo(m_po.add("shrink-to").requiresValue("width,height").description("Shrink each sprite if it is\nlarger than width or height"))
@ -38,7 +41,8 @@ AppOptions::AppOptions(int argc, const char* argv[])
, m_sheetHeight(m_po.add("sheet-height").requiresValue("<pixels>").description("Sprite sheet height"))
, m_sheetType(m_po.add("sheet-type").requiresValue("<type>").description("Algorithm to create the sprite sheet:\n horizontal\n vertical\n rows\n columns\n packed"))
, m_sheetPack(m_po.add("sheet-pack").description("Same as --sheet-type packed"))
, m_splitLayers(m_po.add("split-layers").description("Import each layer of the next given sprite as\na separated image in the sheet"))
, m_splitLayers(m_po.add("split-layers").description("Save each visible layer of sprites\nas separated images in the sheet\n"))
, m_splitTags(m_po.add("split-tags").description("Save each tag as a separated file"))
, m_layer(m_po.add("layer").alias("import-layer").requiresValue("<name>").description("Include just the given layer in the sheet"))
, m_allLayers(m_po.add("all-layers").description("Make all layers visible\nBy default hidden layers will be ignored"))
, m_frameTag(m_po.add("frame-tag").requiresValue("<name>").description("Include tagged frames in the sheet"))
@ -67,18 +71,16 @@ AppOptions::AppOptions(int argc, const char* argv[])
m_verboseLevel = kVerbose;
m_paletteFileName = m_po.value_of(m_palette);
m_startShell = m_po.enabled(m_shell);
m_previewCLI = m_po.enabled(m_preview);
m_showHelp = m_po.enabled(m_help);
m_showVersion = m_po.enabled(m_version);
if (m_po.enabled(m_help)) {
showHelp();
m_startUI = false;
}
else if (m_po.enabled(m_version)) {
showVersion();
m_startUI = false;
}
if (m_po.enabled(m_shell) || m_po.enabled(m_batch)) {
if (m_startShell ||
m_showHelp ||
m_showVersion ||
m_po.enabled(m_batch)) {
m_startUI = false;
}
}
@ -96,21 +98,4 @@ bool AppOptions::hasExporterParams() const
m_po.enabled(m_sheet);
}
void AppOptions::showHelp()
{
std::cout
<< PACKAGE << " v" << VERSION << " | A pixel art program\n" << COPYRIGHT
<< "\n\nUsage:\n"
<< " " << m_exeName << " [OPTIONS] [FILES]...\n\n"
<< "Options:\n"
<< m_po
<< "\nFind more information in " << PACKAGE
<< " web site: " << WEBSITE << "\n\n";
}
void AppOptions::showVersion()
{
std::cout << PACKAGE << ' ' << VERSION << '\n';
}
}

View File

@ -5,8 +5,8 @@
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_APP_OPTIONS_H_INCLUDED
#define APP_APP_OPTIONS_H_INCLUDED
#ifndef APP_CLI_APP_OPTIONS_H_INCLUDED
#define APP_CLI_APP_OPTIONS_H_INCLUDED
#pragma once
#include <stdexcept>
@ -31,8 +31,14 @@ public:
AppOptions(int argc, const char* argv[]);
const std::string& exeName() const { return m_exeName; }
const base::ProgramOptions& programOptions() const { return m_po; }
bool startUI() const { return m_startUI; }
bool startShell() const { return m_startShell; }
bool previewCLI() const { return m_previewCLI; }
bool showHelp() const { return m_showHelp; }
bool showVersion() const { return m_showVersion; }
VerboseLevel verboseLevel() const { return m_verboseLevel; }
const std::string& paletteFileName() const { return m_paletteFileName; }
@ -53,6 +59,7 @@ public:
const Option& sheetType() const { return m_sheetType; }
const Option& sheetPack() const { return m_sheetPack; }
const Option& splitLayers() const { return m_splitLayers; }
const Option& splitTags() const { return m_splitTags; }
const Option& layer() const { return m_layer; }
const Option& allLayers() const { return m_allLayers; }
const Option& frameTag() const { return m_frameTag; }
@ -71,19 +78,20 @@ public:
bool hasExporterParams() const;
private:
void showHelp();
void showVersion();
std::string m_exeName;
base::ProgramOptions m_po;
bool m_startUI;
bool m_startShell;
bool m_previewCLI;
bool m_showHelp;
bool m_showVersion;
VerboseLevel m_verboseLevel;
std::string m_paletteFileName;
Option& m_palette;
Option& m_shell;
Option& m_batch;
Option& m_preview;
Option& m_saveAs;
Option& m_scale;
Option& m_shrinkTo;
@ -95,6 +103,7 @@ private:
Option& m_sheetType;
Option& m_sheetPack;
Option& m_splitLayers;
Option& m_splitTags;
Option& m_layer;
Option& m_allLayers;
Option& m_frameTag;

View File

@ -0,0 +1,37 @@
// Aseprite
// Copyright (C) 2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_CLI_CLI_DELEGATE_H_INCLUDED
#define APP_CLI_CLI_DELEGATE_H_INCLUDED
#pragma once
#include <string>
namespace app {
class AppOptions;
class DocumentExporter;
struct CliOpenFile;
class CliDelegate {
public:
virtual ~CliDelegate() { }
virtual void showHelp(const AppOptions& options) { }
virtual void showVersion() { }
virtual void uiMode() { }
virtual void shellMode() { }
virtual void batchMode() { }
virtual void beforeOpenFile(const CliOpenFile& cof) { }
virtual void afterOpenFile(const CliOpenFile& cof) { }
virtual void saveFile(const CliOpenFile& cof) { }
virtual void exportFiles(DocumentExporter& exporter) { }
virtual void execScript(const std::string& filename) { }
};
} // namespace app
#endif

View File

@ -0,0 +1,43 @@
// Aseprite
// Copyright (C) 2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cli/cli_open_file.h"
#include "app/document.h"
#include "app/file/file.h"
#include "doc/frame_tag.h"
#include "doc/frame_tags.h"
#include "doc/sprite.h"
namespace app {
CliOpenFile::CliOpenFile()
{
document = nullptr;
fromFrame = -1;
toFrame = -1;
splitLayers = false;
splitTags = false;
allLayers = false;
listLayers = false;
listTags = false;
ignoreEmpty = false;
trim = false;
crop = gfx::Rect();
}
FileOpROI CliOpenFile::roi() const
{
ASSERT(document);
return FileOpROI(document, frameTag, fromFrame, toFrame);
}
} // namespace app

View File

@ -0,0 +1,53 @@
// Aseprite
// Copyright (C) 2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_CLI_CLI_OPEN_FILE_H_INCLUDED
#define APP_CLI_CLI_OPEN_FILE_H_INCLUDED
#pragma once
#include "doc/frame.h"
#include "gfx/rect.h"
#include <string>
namespace app {
class Document;
class FileOpROI;
struct CliOpenFile {
app::Document* document;
std::string filename;
std::string filenameFormat;
std::string frameTag;
std::string importLayer;
doc::frame_t fromFrame, toFrame;
bool splitLayers;
bool splitTags;
bool allLayers;
bool listLayers;
bool listTags;
bool ignoreEmpty;
bool trim;
gfx::Rect crop;
CliOpenFile();
bool hasFrameTag() const {
return (!frameTag.empty());
}
bool hasFrameRange() const {
return (fromFrame >= 0 && toFrame >= 0);
}
FileOpROI roi() const;
};
} // namespace app
#endif

View File

@ -0,0 +1,540 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cli/cli_processor.h"
#include "app/cli/app_options.h"
#include "app/cli/cli_delegate.h"
#include "app/commands/cmd_sprite_size.h"
#include "app/commands/commands.h"
#include "app/commands/params.h"
#include "app/console.h"
#include "app/document.h"
#include "app/document_exporter.h"
#include "app/document_undo.h"
#include "app/file/file.h"
#include "app/filename_formatter.h"
#include "app/ui_context.h"
#include "base/convert_to.h"
#include "base/path.h"
#include "base/split_string.h"
#include "doc/frame_tag.h"
#include "doc/frame_tags.h"
#include "doc/layer.h"
#include "doc/layers_range.h"
namespace app {
CliProcessor::CliProcessor(CliDelegate* delegate,
const AppOptions& options)
: m_delegate(delegate)
, m_options(options)
, m_exporter(nullptr)
{
if (options.hasExporterParams())
m_exporter.reset(new DocumentExporter);
}
void CliProcessor::process()
{
// --help
if (m_options.showHelp()) {
m_delegate->showHelp(m_options);
}
// --version
else if (m_options.showVersion()) {
m_delegate->showVersion();
}
// Process other options and file names
else if (!m_options.values().empty()) {
Console console;
UIContext* ctx = UIContext::instance();
CliOpenFile cof;
SpriteSheetType sheetType = SpriteSheetType::None;
app::Document* lastDoc = nullptr;
for (const auto& value : m_options.values()) {
const AppOptions::Option* opt = value.option();
// Special options/commands
if (opt) {
// --data <file.json>
if (opt == &m_options.data()) {
if (m_exporter)
m_exporter->setDataFilename(value.value());
}
// --format <format>
else if (opt == &m_options.format()) {
if (m_exporter) {
DocumentExporter::DataFormat format = DocumentExporter::DefaultDataFormat;
if (value.value() == "json-hash")
format = DocumentExporter::JsonHashDataFormat;
else if (value.value() == "json-array")
format = DocumentExporter::JsonArrayDataFormat;
m_exporter->setDataFormat(format);
}
}
// --sheet <file.png>
else if (opt == &m_options.sheet()) {
if (m_exporter)
m_exporter->setTextureFilename(value.value());
}
// --sheet-width <width>
else if (opt == &m_options.sheetWidth()) {
if (m_exporter)
m_exporter->setTextureWidth(strtol(value.value().c_str(), NULL, 0));
}
// --sheet-height <height>
else if (opt == &m_options.sheetHeight()) {
if (m_exporter)
m_exporter->setTextureHeight(strtol(value.value().c_str(), NULL, 0));
}
// --sheet-pack
else if (opt == &m_options.sheetType()) {
if (value.value() == "horizontal")
sheetType = SpriteSheetType::Horizontal;
else if (value.value() == "vertical")
sheetType = SpriteSheetType::Vertical;
else if (value.value() == "rows")
sheetType = SpriteSheetType::Rows;
else if (value.value() == "columns")
sheetType = SpriteSheetType::Columns;
else if (value.value() == "packed")
sheetType = SpriteSheetType::Packed;
}
// --sheet-pack
else if (opt == &m_options.sheetPack()) {
sheetType = SpriteSheetType::Packed;
}
// --split-layers
else if (opt == &m_options.splitLayers()) {
cof.splitLayers = true;
}
// --split-tags
else if (opt == &m_options.splitTags()) {
cof.splitTags = true;
}
// --layer <layer-name>
else if (opt == &m_options.layer()) {
cof.importLayer = value.value();
}
// --all-layers
else if (opt == &m_options.allLayers()) {
cof.allLayers = true;
}
// --frame-tag <tag-name>
else if (opt == &m_options.frameTag()) {
cof.frameTag = value.value();
}
// --frame-range from,to
else if (opt == &m_options.frameRange()) {
std::vector<std::string> splitRange;
base::split_string(value.value(), splitRange, ",");
if (splitRange.size() < 2)
throw std::runtime_error("--frame-range needs two parameters separated by comma (,)\n"
"Usage: --frame-range from,to\n"
"E.g. --frame-range 0,99");
cof.fromFrame = base::convert_to<frame_t>(splitRange[0]);
cof.toFrame = base::convert_to<frame_t>(splitRange[1]);
}
// --ignore-empty
else if (opt == &m_options.ignoreEmpty()) {
cof.ignoreEmpty = true;
if (m_exporter)
m_exporter->setIgnoreEmptyCels(true);
}
// --border-padding
else if (opt == &m_options.borderPadding()) {
if (m_exporter)
m_exporter->setBorderPadding(strtol(value.value().c_str(), NULL, 0));
}
// --shape-padding
else if (opt == &m_options.shapePadding()) {
if (m_exporter)
m_exporter->setShapePadding(strtol(value.value().c_str(), NULL, 0));
}
// --inner-padding
else if (opt == &m_options.innerPadding()) {
if (m_exporter)
m_exporter->setInnerPadding(strtol(value.value().c_str(), NULL, 0));
}
// --trim
else if (opt == &m_options.trim()) {
cof.trim = true;
if (m_exporter)
m_exporter->setTrimCels(true);
}
// --crop x,y,width,height
else if (opt == &m_options.crop()) {
std::vector<std::string> parts;
base::split_string(value.value(), parts, ",");
if (parts.size() < 4)
throw std::runtime_error("--crop needs four parameters separated by comma (,)\n"
"Usage: --crop x,y,width,height\n"
"E.g. --crop 0,0,32,32");
cof.crop.x = base::convert_to<int>(parts[0]);
cof.crop.y = base::convert_to<int>(parts[1]);
cof.crop.w = base::convert_to<int>(parts[2]);
cof.crop.h = base::convert_to<int>(parts[3]);
}
// --filename-format
else if (opt == &m_options.filenameFormat()) {
cof.filenameFormat = value.value();
if (m_exporter)
m_exporter->setFilenameFormat(cof.filenameFormat);
}
// --save-as <filename>
else if (opt == &m_options.saveAs()) {
if (lastDoc) {
std::string fn = value.value();
// Automatic --split-layer or --split-tags in case the
// output filename already contains {layer} or {tag}
// template elements.
bool hasLayerTemplate = (fn.find("{layer}") != std::string::npos);
bool hasTagTemplate = (fn.find("{tag}") != std::string::npos);
if (hasLayerTemplate || hasTagTemplate) {
cof.splitLayers = (cof.splitLayers || hasLayerTemplate);
cof.splitTags = (cof.splitTags || hasTagTemplate);
cof.filenameFormat =
get_default_filename_format(
fn,
true, // With path
(lastDoc->sprite()->totalFrames() > 1), // Has frames
false, // Has layer
false); // Has frame tag
}
cof.document = lastDoc;
cof.filename = fn;
saveFile(cof);
}
else
console.printf("A document is needed before --save-as argument\n");
}
// --scale <factor>
else if (opt == &m_options.scale()) {
Command* command = CommandsModule::instance()->getCommandByName(CommandId::SpriteSize);
double scale = strtod(value.value().c_str(), NULL);
static_cast<SpriteSizeCommand*>(command)->setScale(scale, scale);
// Scale all sprites
for (auto doc : ctx->documents()) {
ctx->setActiveDocument(static_cast<app::Document*>(doc));
ctx->executeCommand(command);
}
}
// --shrink-to <width,height>
else if (opt == &m_options.shrinkTo()) {
std::vector<std::string> dimensions;
base::split_string(value.value(), dimensions, ",");
if (dimensions.size() < 2)
throw std::runtime_error("--shrink-to needs two parameters separated by comma (,)\n"
"Usage: --shrink-to width,height\n"
"E.g. --shrink-to 128,64");
double maxWidth = base::convert_to<double>(dimensions[0]);
double maxHeight = base::convert_to<double>(dimensions[1]);
double scaleWidth, scaleHeight, scale;
// Shrink all sprites if needed
for (auto doc : ctx->documents()) {
ctx->setActiveDocument(static_cast<app::Document*>(doc));
scaleWidth = (doc->width() > maxWidth ? maxWidth / doc->width() : 1.0);
scaleHeight = (doc->height() > maxHeight ? maxHeight / doc->height() : 1.0);
if (scaleWidth < 1.0 || scaleHeight < 1.0) {
scale = MIN(scaleWidth, scaleHeight);
Command* command = CommandsModule::instance()->getCommandByName(CommandId::SpriteSize);
static_cast<SpriteSizeCommand*>(command)->setScale(scale, scale);
ctx->executeCommand(command);
}
}
}
// --script <filename>
else if (opt == &m_options.script()) {
std::string filename = value.value();
m_delegate->execScript(filename);
}
// --list-layers
else if (opt == &m_options.listLayers()) {
cof.listLayers = true;
if (m_exporter)
m_exporter->setListLayers(true);
}
// --list-tags
else if (opt == &m_options.listTags()) {
cof.listTags = true;
if (m_exporter)
m_exporter->setListFrameTags(true);
}
}
// File names aren't associated to any option
else {
cof.document = nullptr;
cof.filename = base::normalize_path(value.value());
if (openFile(cof))
lastDoc = cof.document;
}
}
if (m_exporter) {
if (sheetType != SpriteSheetType::None)
m_exporter->setSpriteSheetType(sheetType);
m_delegate->exportFiles(*m_exporter.get());
m_exporter.reset(nullptr);
}
}
// Running mode
if (m_options.startUI()) {
m_delegate->uiMode();
}
else if (m_options.startShell()) {
m_delegate->shellMode();
}
else {
m_delegate->batchMode();
}
}
bool CliProcessor::openFile(CliOpenFile& cof)
{
m_delegate->beforeOpenFile(cof);
Context* ctx = UIContext::instance();
app::Document* oldDoc = ctx->activeDocument();
Command* openCommand = CommandsModule::instance()->getCommandByName(CommandId::OpenFile);
Params params;
params.set("filename", cof.filename.c_str());
ctx->executeCommand(openCommand, params);
app::Document* doc = ctx->activeDocument();
// If the active document is equal to the previous one, it
// means that we couldn't open this specific document.
if (doc == oldDoc)
doc = nullptr;
cof.document = doc;
if (doc) {
// Show all layers
if (cof.allLayers) {
for (doc::Layer* layer : doc->sprite()->layers())
layer->setVisible(true);
}
// Add document to exporter
if (m_exporter) {
FrameTag* frameTag = nullptr;
bool isTemporalTag = false;
if (cof.hasFrameTag()) {
frameTag = doc->sprite()->frameTags().getByName(cof.frameTag);
}
if (cof.hasFrameRange()) {
// --frame-range with --frame-tag
if (frameTag) {
frameTag = new FrameTag(
frameTag->fromFrame()+MID(0, cof.fromFrame, frameTag->frames()-1),
frameTag->fromFrame()+MID(0, cof.toFrame, frameTag->frames()-1));
}
// --frame-range without --frame-tag
else {
frameTag = new FrameTag(cof.fromFrame, cof.toFrame);
}
isTemporalTag = true;
}
if (!cof.importLayer.empty()) {
Layer* foundLayer = nullptr;
for (Layer* layer : doc->sprite()->layers()) {
if (layer->name() == cof.importLayer) {
foundLayer = layer;
break;
}
}
if (foundLayer)
m_exporter->addDocument(doc, foundLayer, frameTag, isTemporalTag);
}
else if (cof.splitLayers) {
for (auto layer : doc->sprite()->layers()) {
if (layer->isVisible())
m_exporter->addDocument(doc, layer, frameTag, isTemporalTag);
}
}
else {
m_exporter->addDocument(doc, nullptr, frameTag, isTemporalTag);
}
}
}
m_delegate->afterOpenFile(cof);
return (doc ? true: false);
}
void CliProcessor::saveFile(const CliOpenFile& cof)
{
UIContext* ctx = UIContext::instance();
ctx->setActiveDocument(cof.document);
Command* trimCommand = CommandsModule::instance()->getCommandByName(CommandId::AutocropSprite);
Command* cropCommand = CommandsModule::instance()->getCommandByName(CommandId::CropSprite);
Command* undoCommand = CommandsModule::instance()->getCommandByName(CommandId::Undo);
app::Document* doc = cof.document;
bool clearUndo = false;
if (!cof.crop.isEmpty()) {
Params cropParams;
cropParams.set("x", base::convert_to<std::string>(cof.crop.x).c_str());
cropParams.set("y", base::convert_to<std::string>(cof.crop.y).c_str());
cropParams.set("width", base::convert_to<std::string>(cof.crop.w).c_str());
cropParams.set("height", base::convert_to<std::string>(cof.crop.h).c_str());
ctx->executeCommand(cropCommand, cropParams);
}
// Store in "visibility" the original "visible" state of every layer.
std::vector<bool> visibility(doc->sprite()->countLayers());
int i = 0;
for (doc::Layer* layer : doc->sprite()->layers())
visibility[i++] = layer->isVisible();
std::string fn = cof.filename;
std::string filenameFormat = cof.filenameFormat;
if (filenameFormat.empty()) { // Default format
bool hasFrames = (cof.roi().frames() > 1);
filenameFormat = get_default_filename_format(
fn,
true, // With path
hasFrames, // Has frames
cof.splitLayers, // Has layer
cof.splitTags); // Has frame tag
}
std::vector<doc::Layer*> layers;
// --save-as with --split-layers or --split-tags
if (cof.splitLayers) {
for (doc::Layer* layer : doc->sprite()->layers())
layers.push_back(layer);
}
else {
// Show only one layer
if (!cof.importLayer.empty()) {
for (Layer* layer : doc->sprite()->layers()) {
if (layer->name() == cof.importLayer) {
layer->setVisible(true);
layers.push_back(layer);
}
else
layer->setVisible(false);
}
}
// All visible layers
else
layers.push_back(nullptr);
}
std::vector<doc::FrameTag*> frameTags;
if (cof.hasFrameTag()) {
frameTags.push_back(
doc->sprite()->frameTags().getByName(cof.frameTag));
}
else {
doc::FrameTags& origFrameTags = cof.document->sprite()->frameTags();
if (cof.splitTags && !origFrameTags.empty()) {
for (doc::FrameTag* frameTag : origFrameTags) {
// In case the tag is outside the given --frame-range
if (cof.hasFrameRange()) {
if (frameTag->toFrame() < cof.fromFrame ||
frameTag->fromFrame() > cof.toFrame)
continue;
}
frameTags.push_back(frameTag);
}
}
else
frameTags.push_back(nullptr);
}
for (doc::FrameTag* frameTag : frameTags) {
// For each layer, hide other ones and save the sprite.
i = 0;
for (doc::Layer* layer : layers) {
if (cof.splitLayers) {
ASSERT(layer);
// If the user doesn't want all layers and this one is hidden.
if (!visibility[i++])
continue; // Just ignore this layer.
// Make this layer ("show") the only one visible.
for (doc::Layer* hide : doc->sprite()->layers())
hide->setVisible(hide == layer);
}
// TODO --trim --save-as --split-layers doesn't make too much
// sense as we lost the trim rectangle information (e.g. we
// don't have sheet .json) Also, we should trim each frame
// individually (a process that can be done only in
// FileOp::operate()).
if (cof.trim)
ctx->executeCommand(trimCommand);
CliOpenFile itemCof = cof;
FilenameInfo fnInfo;
fnInfo.filename(fn);
if (layer) {
fnInfo.layerName(layer->name());
itemCof.importLayer = layer->name();
}
if (frameTag) {
fnInfo
.innerTagName(frameTag->name())
.outerTagName(frameTag->name());
itemCof.frameTag = frameTag->name();
}
itemCof.filename = filename_formatter(filenameFormat, fnInfo);
itemCof.filenameFormat = filename_formatter(filenameFormat, fnInfo, false);
// Call delegate
m_delegate->saveFile(itemCof);
if (cof.trim) {
ctx->executeCommand(undoCommand);
clearUndo = true;
}
}
}
// Restore layer visibility
i = 0;
for (Layer* layer : doc->sprite()->layers())
layer->setVisible(visibility[i++]);
// Undo crop
if (!cof.crop.isEmpty()) {
ctx->executeCommand(undoCommand);
clearUndo = true;
}
if (clearUndo) {
// Just in case allow non-linear history is enabled
// we clear redo information
doc->undoHistory()->clearRedo();
}
}
} // namespace app

View File

@ -0,0 +1,41 @@
// Aseprite
// Copyright (C) 2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_APP_CLI_PROCESSOR_H_INCLUDED
#define APP_APP_CLI_PROCESSOR_H_INCLUDED
#pragma once
#include "app/cli/cli_delegate.h"
#include "app/cli/cli_open_file.h"
#include "base/unique_ptr.h"
#include <string>
#include <vector>
namespace app {
class AppOptions;
class DocumentExporter;
class CliProcessor {
public:
CliProcessor(CliDelegate* delegate,
const AppOptions& options);
void process();
private:
bool openFile(CliOpenFile& cof);
void saveFile(const CliOpenFile& cof);
CliDelegate* m_delegate;
const AppOptions& m_options;
base::UniquePtr<DocumentExporter> m_exporter;
};
} // namespace app
#endif

87
src/app/cli/cli_tests.cpp Normal file
View File

@ -0,0 +1,87 @@
// Aseprite
// Copyright (C) 2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#include "tests/test.h"
#include "app/cli/app_options.h"
#include "app/cli/cli_processor.h"
#include "app/document_exporter.h"
#include <initializer_list>
using namespace app;
class CliTestDelegate : public CliDelegate {
public:
CliTestDelegate() {
m_helpWasShown = false;
m_versionWasShown = false;
m_uiMode = false;
m_shellMode = false;
m_batchMode = false;
}
void showHelp(const AppOptions& options) override { m_helpWasShown = true; }
void showVersion() override { m_versionWasShown = true; }
void uiMode() override { m_uiMode = true; }
void shellMode() override { m_shellMode = true; }
void batchMode() override { m_batchMode = true; }
void beforeOpenFile(const CliOpenFile& cof) override { }
void afterOpenFile(const CliOpenFile& cof) override { }
void saveFile(const CliOpenFile& cof) override { }
void exportFiles(DocumentExporter& exporter) override { }
void execScript(const std::string& filename) override { }
bool helpWasShown() const { return m_helpWasShown; }
bool versionWasShown() const { return m_versionWasShown; }
private:
bool m_helpWasShown;
bool m_versionWasShown;
bool m_uiMode;
bool m_shellMode;
bool m_batchMode;
};
AppOptions args(std::initializer_list<const char*> l) {
int argc = l.size()+1;
const char** argv = new const char*[argc];
argv[0] = "aseprite.exe";
auto it = l.begin();
for (int i=1; i<argc; ++i, ++it) {
argv[i] = *it;
TRACE("argv[%d] = %s\n", i, argv[i]);
}
AppOptions opts(argc, argv);
delete[] argv;
return opts;
}
TEST(Cli, None)
{
CliTestDelegate d;
CliProcessor p(&d, args({ }));
p.process();
EXPECT_TRUE(!d.helpWasShown());
EXPECT_TRUE(!d.versionWasShown());
}
TEST(Cli, Help)
{
CliTestDelegate d;
CliProcessor p(&d, args({ "--help" }));
p.process();
EXPECT_TRUE(d.helpWasShown());
}
TEST(Cli, Version)
{
CliTestDelegate d;
CliProcessor p(&d, args({ "--version" }));
p.process();
EXPECT_TRUE(d.versionWasShown());
}

View File

@ -0,0 +1,105 @@
// Aseprite
// Copyright (C) 2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cli/default_cli_delegate.h"
#include "app/cli/app_options.h"
#include "app/cli/cli_open_file.h"
#include "app/commands/commands.h"
#include "app/commands/params.h"
#include "app/document.h"
#include "app/document_exporter.h"
#include "app/script/app_scripting.h"
#include "app/ui_context.h"
#include "base/convert_to.h"
#include "doc/frame_tag.h"
#include "doc/layer.h"
#include "doc/layers_range.h"
#include "doc/sprite.h"
#include "script/engine_delegate.h"
#include <iostream>
namespace app {
void DefaultCliDelegate::showHelp(const AppOptions& options)
{
std::cout
<< PACKAGE << " v" << VERSION << " | A pixel art program\n" << COPYRIGHT
<< "\n\nUsage:\n"
<< " " << options.exeName() << " [OPTIONS] [FILES]...\n\n"
<< "Options:\n"
<< options.programOptions()
<< "\nFind more information in " << PACKAGE
<< " web site: " << WEBSITE << "\n\n";
}
void DefaultCliDelegate::showVersion()
{
std::cout << PACKAGE << ' ' << VERSION << '\n';
}
void DefaultCliDelegate::afterOpenFile(const CliOpenFile& cof)
{
if (!cof.document) // Do nothing
return;
if (cof.listLayers) {
for (doc::Layer* layer : cof.document->sprite()->layers()) {
if (layer->isVisible())
std::cout << layer->name() << "\n";
}
}
if (cof.listTags) {
for (doc::FrameTag* tag : cof.document->sprite()->frameTags())
std::cout << tag->name() << "\n";
}
}
void DefaultCliDelegate::saveFile(const CliOpenFile& cof)
{
Context* ctx = UIContext::instance();
Command* saveAsCommand = CommandsModule::instance()->getCommandByName(CommandId::SaveFileCopyAs);
Params params;
params.set("filename", cof.filename.c_str());
params.set("filename-format", cof.filenameFormat.c_str());
if (cof.hasFrameTag()) {
params.set("frame-tag", cof.frameTag.c_str());
}
if (cof.hasFrameRange()) {
params.set("from-frame", base::convert_to<std::string>(cof.fromFrame).c_str());
params.set("to-frame", base::convert_to<std::string>(cof.toFrame).c_str());
}
ctx->executeCommand(saveAsCommand, params);
}
void DefaultCliDelegate::exportFiles(DocumentExporter& exporter)
{
LOG("Exporting sheet...\n");
base::UniquePtr<app::Document> spriteSheet(exporter.exportSheet());
// Sprite sheet isn't used, we just delete it.
LOG("Export sprite sheet: Done\n");
}
void DefaultCliDelegate::execScript(const std::string& filename)
{
script::StdoutEngineDelegate delegate;
AppScripting engine(&delegate);
engine.evalFile(filename);
}
} // namespace app

View File

@ -0,0 +1,28 @@
// Aseprite
// Copyright (C) 2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_CLI_DEFAULT_CLI_DELEGATE_H_INCLUDED
#define APP_CLI_DEFAULT_CLI_DELEGATE_H_INCLUDED
#pragma once
#include "app/cli/cli_delegate.h"
namespace app {
class DefaultCliDelegate : public CliDelegate {
public:
void showHelp(const AppOptions& programOptions) override;
void showVersion() override;
void afterOpenFile(const CliOpenFile& cof) override;
void saveFile(const CliOpenFile& cof) override;
void exportFiles(DocumentExporter& exporter) override;
void execScript(const std::string& filename) override;
};
} // namespace app
#endif

View File

@ -0,0 +1,181 @@
// Aseprite
// Copyright (C) 2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cli/preview_cli_delegate.h"
#include "app/cli/cli_open_file.h"
#include "app/document.h"
#include "app/document_exporter.h"
#include "app/file/file.h"
#include "app/ui_context.h"
#include "base/fs.h"
#include "base/unique_ptr.h"
#include "doc/sprite.h"
#include <iostream>
namespace app {
void PreviewCliDelegate::showHelp(const AppOptions& options)
{
std::cout << "- Show " PACKAGE " CLI usage\n";
}
void PreviewCliDelegate::showVersion()
{
std::cout << "- Show " PACKAGE " version\n";
}
void PreviewCliDelegate::uiMode()
{
std::cout << "- Run UI mode\n";
}
void PreviewCliDelegate::shellMode()
{
std::cout << "- Run shell mode\n";
}
void PreviewCliDelegate::batchMode()
{
std::cout << "- Exit\n";
}
void PreviewCliDelegate::beforeOpenFile(const CliOpenFile& cof)
{
std::cout << "- Open file '" << cof.filename << "'\n";
}
void PreviewCliDelegate::afterOpenFile(const CliOpenFile& cof)
{
if (!cof.document) {
std::cout << " - WARNING: File not found or error loading file\n";
return;
}
if (cof.listLayers)
std::cout << " - List layers\n";
if (cof.listTags)
std::cout << " - List tags\n";
if (cof.allLayers)
std::cout << " - Make all layers visible\n";
if (!cof.importLayer.empty())
std::cout << " - Make layer '" << cof.importLayer << "' visible only (hide all other layers)\n";
}
void PreviewCliDelegate::saveFile(const CliOpenFile& cof)
{
ASSERT(cof.document);
ASSERT(cof.document->sprite());
std::cout << "- Save file '" << cof.filename << "'\n"
<< " - Sprite: '" << cof.document->filename() << "'\n";
if (!cof.crop.isEmpty()) {
std::cout << " - Crop: "
<< cof.crop.x << ","
<< cof.crop.y << " "
<< cof.crop.w << "x"
<< cof.crop.h << "\n";
}
if (cof.trim) {
std::cout << " - Trim\n";
}
std::cout << " - Size: "
<< cof.document->sprite()->width() << "x"
<< cof.document->sprite()->height() << "\n";
if (!cof.importLayer.empty())
std::cout << " - Layer: '" << cof.importLayer << "'\n";
if (cof.hasFrameTag()) {
std::cout << " - Frame tag: '" << cof.frameTag << "'\n";
}
if (cof.hasFrameRange()) {
auto roi = cof.roi();
std::cout << " - Frame range from "
<< roi.fromFrame() << " to "
<< roi.toFrame() << "\n";
}
if (!cof.filenameFormat.empty())
std::cout << " - Filename format: '" << cof.filenameFormat << "'\n";
base::UniquePtr<FileOp> fop(
FileOp::createSaveDocumentOperation(
UIContext::instance(),
cof.roi(),
cof.filename.c_str(),
cof.filenameFormat.c_str()));
if (fop) {
std::vector<std::string> files;
fop->getFilenameList(files);
for (const auto& file : files) {
if (base::is_file(file))
std::cout << " - Overwrite file: '" << file << "'\n";
else
std::cout << " - Output file: '" << file << "'\n";
}
}
else
std::cout << " - No output\n";
}
void PreviewCliDelegate::exportFiles(DocumentExporter& exporter)
{
std::string type = "None";
switch (exporter.spriteSheetType()) {
case SpriteSheetType::Horizontal: type = "Horizontal"; break;
case SpriteSheetType::Vertical: type = "Vertical"; break;
case SpriteSheetType::Rows: type = "Rows"; break;
case SpriteSheetType::Columns: type = "Columns"; break;
case SpriteSheetType::Packed: type = "Packed"; break;
}
gfx::Size size = exporter.calculateSheetSize();
std::cout << "- Export sprite sheet:\n"
<< " - Type: " << type << "\n"
<< " - Size: " << size.w << "x" << size.h << "\n";
if (!exporter.textureFilename().empty()) {
std::cout << " - Save texture file: '"
<< exporter.textureFilename() << "'\n";
}
if (!exporter.dataFilename().empty()) {
std::string format = "Unknown";
switch (exporter.dataFormat()) {
case DocumentExporter::JsonHashDataFormat: format = "JSON Hash"; break;
case DocumentExporter::JsonArrayDataFormat: format = "JSON Array"; break;
}
std::cout << " - Save data file: '" << exporter.dataFilename() << "'\n"
<< " - Data format: " << format << "\n";
if (!exporter.filenameFormat().empty()) {
std::cout << " - Filename format for JSON items: '"
<< exporter.filenameFormat() << "'\n";
}
}
}
void PreviewCliDelegate::execScript(const std::string& filename)
{
std::cout << "- Run script: '" << filename << "'\n";
}
} // namespace app

View File

@ -0,0 +1,32 @@
// Aseprite
// Copyright (C) 2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_CLI_PREVIEW_CLI_DELEGATE_H_INCLUDED
#define APP_CLI_PREVIEW_CLI_DELEGATE_H_INCLUDED
#pragma once
#include "app/cli/cli_delegate.h"
namespace app {
class PreviewCliDelegate : public CliDelegate {
public:
void showHelp(const AppOptions& programOptions) override;
void showVersion() override;
void uiMode() override;
void shellMode() override;
void batchMode() override;
void beforeOpenFile(const CliOpenFile& cof) override;
void afterOpenFile(const CliOpenFile& cof) override;
void saveFile(const CliOpenFile& cof) override;
void exportFiles(DocumentExporter& exporter) override;
void execScript(const std::string& filename) override;
};
} // namespace app
#endif

View File

@ -94,43 +94,6 @@ private:
FileOp* m_fop;
};
static void save_document_in_background(const Context* context,
const Document* document, bool mark_as_saved,
const std::string& fn_format)
{
base::UniquePtr<FileOp> fop(
FileOp::createSaveDocumentOperation(
context, document,
document->filename().c_str(), fn_format.c_str()));
if (!fop)
return;
SaveFileJob job(fop);
job.showProgressWindow();
if (fop->hasError()) {
Console console;
console.printf(fop->error().c_str());
// We don't know if the file was saved correctly or not. So mark
// it as it should be saved again.
const_cast<Document*>(document)->impossibleToBackToSavedState();
}
// If the job was cancelled, mark the document as modified.
else if (fop->isStop()) {
const_cast<Document*>(document)->impossibleToBackToSavedState();
}
else if (context->isUIAvailable()) {
App::instance()->recentFiles()->addRecentFile(document->filename().c_str());
if (mark_as_saved)
const_cast<Document*>(document)->markAsSaved();
StatusBar::instance()
->setStatusText(2000, "File %s, saved.",
document->name().c_str());
}
}
//////////////////////////////////////////////////////////////////////
SaveFileBaseCommand::SaveFileBaseCommand(const char* short_name, const char* friendly_name, CommandFlags flags)
@ -142,6 +105,14 @@ void SaveFileBaseCommand::onLoadParams(const Params& params)
{
m_filename = params.get("filename");
m_filenameFormat = params.get("filename-format");
m_frameTag = params.get("frame-tag");
m_fromFrame = m_toFrame = -1;
if (params.has_param("from-frame") ||
params.has_param("to-frame")) {
m_fromFrame = params.get_as<doc::frame_t>("from-frame");
m_toFrame = params.get_as<doc::frame_t>("to-frame");
}
}
// Returns true if there is a current sprite to save.
@ -222,9 +193,7 @@ bool SaveFileBaseCommand::saveAsDialog(Context* context,
}
// Save the document
save_document_in_background(
context, const_cast<Document*>(document),
markAsSaved, m_filenameFormat);
saveDocumentInBackground(context, const_cast<Document*>(document), markAsSaved);
// Undo resize
if (undoResize) {
@ -246,6 +215,45 @@ bool SaveFileBaseCommand::saveAsDialog(Context* context,
return true;
}
void SaveFileBaseCommand::saveDocumentInBackground(const Context* context,
const app::Document* document,
bool markAsSaved) const
{
base::UniquePtr<FileOp> fop(
FileOp::createSaveDocumentOperation(
context,
FileOpROI(document, m_frameTag, m_fromFrame, m_toFrame),
document->filename().c_str(),
m_filenameFormat.c_str()));
if (!fop)
return;
SaveFileJob job(fop);
job.showProgressWindow();
if (fop->hasError()) {
Console console;
console.printf(fop->error().c_str());
// We don't know if the file was saved correctly or not. So mark
// it as it should be saved again.
const_cast<Document*>(document)->impossibleToBackToSavedState();
}
// If the job was cancelled, mark the document as modified.
else if (fop->isStop()) {
const_cast<Document*>(document)->impossibleToBackToSavedState();
}
else if (context->isUIAvailable()) {
App::instance()->recentFiles()->addRecentFile(document->filename().c_str());
if (markAsSaved)
const_cast<Document*>(document)->markAsSaved();
StatusBar::instance()
->setStatusText(2000, "File %s, saved.",
document->name().c_str());
}
}
//////////////////////////////////////////////////////////////////////
class SaveFileCommand : public SaveFileBaseCommand {
@ -274,9 +282,7 @@ void SaveFileCommand::onExecute(Context* context)
ContextWriter writer(context);
Document* documentWriter = writer.document();
save_document_in_background(
context, documentWriter, true,
m_filenameFormat.c_str());
saveDocumentInBackground(context, documentWriter, true);
}
// If the document isn't associated to a file, we must to show the
// save-as dialog to the user to select for first time the file-name

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -10,10 +10,12 @@
#pragma once
#include "app/commands/command.h"
#include "doc/frame.h"
#include <string>
namespace app {
class Document;
class FileSelectorDelegate;
class SaveFileBaseCommand : public Command {
@ -30,10 +32,15 @@ namespace app {
bool saveAsDialog(Context* context, const char* dlgTitle,
FileSelectorDelegate* delegate = nullptr);
void saveDocumentInBackground(const Context* context,
const app::Document* document,
bool markAsSaved) const;
std::string m_filename;
std::string m_filenameFormat;
std::string m_selectedFilename;
std::string m_frameTag;
doc::frame_t m_fromFrame, m_toFrame;
};
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -341,12 +341,9 @@ public:
DocumentExporter::DocumentExporter()
: m_dataFormat(DefaultDataFormat)
, m_textureFormat(DefaultTextureFormat)
, m_textureWidth(0)
, m_textureHeight(0)
, m_sheetType(SpriteSheetType::None)
, m_scale(1.0)
, m_scaleMode(DefaultScaleMode)
, m_ignoreEmptyCels(false)
, m_borderPadding(0)
, m_shapePadding(0)
@ -357,6 +354,14 @@ DocumentExporter::DocumentExporter()
{
}
DocumentExporter::~DocumentExporter()
{
for (auto& item : m_documents) {
if (item.temporalTag)
delete item.frameTag;
}
}
Document* DocumentExporter::exportSheet()
{
// We output the metadata to std::cout if the user didn't specify a file.
@ -384,22 +389,7 @@ Document* DocumentExporter::exportSheet()
}
// 2) Layout those samples in a texture field.
switch (m_sheetType) {
case SpriteSheetType::Packed: {
BestFitLayoutSamples layout;
layout.layoutSamples(
samples, m_borderPadding, m_shapePadding,
m_textureWidth, m_textureHeight);
break;
}
default: {
SimpleLayoutSamples layout(m_sheetType);
layout.layoutSamples(
samples, m_borderPadding, m_shapePadding,
m_textureWidth, m_textureHeight);
break;
}
}
layoutSamples(samples);
// 3) Create and render the texture.
base::UniquePtr<Document> textureDocument(
@ -426,7 +416,15 @@ Document* DocumentExporter::exportSheet()
return textureDocument.release();
}
void DocumentExporter::captureSamples(Samples& samples)
gfx::Size DocumentExporter::calculateSheetSize()
{
Samples samples;
captureSamples(samples);
layoutSamples(samples);
return calculateSheetSize(samples);
}
void DocumentExporter::captureSamples(Samples& samples) const
{
for (auto& item : m_documents) {
Document* doc = item.doc;
@ -434,21 +432,14 @@ void DocumentExporter::captureSamples(Samples& samples)
Layer* layer = item.layer;
FrameTag* frameTag = item.frameTag;
int frames = item.frames();
bool hasFrames = (frames > 1);
bool hasLayer = (layer != nullptr);
bool hasFrameTag = (frameTag && !item.temporalTag);
std::string format = m_filenameFormat;
if (format.empty()) {
if (hasFrames || hasLayer | hasFrameTag) {
format = "{title}";
if (hasLayer ) format += " ({layer})";
if (hasFrameTag) format += " #{tag}";
if (hasFrames ) format += " {frame}";
format += ".{extension}";
}
else
format = "{name}";
format = get_default_filename_format_for_sheet(
doc->filename(),
(frames > 1), // Has frames
(layer != nullptr), // Has layer
(frameTag && !item.temporalTag)); // Has frame tag
}
frame_t frameFirst = item.fromFrame();
@ -543,35 +534,32 @@ void DocumentExporter::captureSamples(Samples& samples)
}
}
Document* DocumentExporter::createEmptyTexture(const Samples& samples)
void DocumentExporter::layoutSamples(Samples& samples)
{
Palette* palette = NULL;
PixelFormat pixelFormat = IMAGE_INDEXED;
gfx::Rect fullTextureBounds(0, 0, m_textureWidth, m_textureHeight);
int maxColors = 256;
for (Samples::const_iterator
it = samples.begin(),
end = samples.end(); it != end; ++it) {
// We try to render an indexed image. But if we find a sprite with
// two or more palettes, or two of the sprites have different
// palettes, we've to use RGB format.
if (pixelFormat == IMAGE_INDEXED) {
if (it->sprite()->pixelFormat() != IMAGE_INDEXED) {
pixelFormat = IMAGE_RGB;
}
else if (it->sprite()->getPalettes().size() > 1) {
pixelFormat = IMAGE_RGB;
}
else if (palette != NULL
&& palette->countDiff(it->sprite()->palette(frame_t(0)), NULL, NULL) > 0) {
pixelFormat = IMAGE_RGB;
}
else
palette = it->sprite()->palette(frame_t(0));
switch (m_sheetType) {
case SpriteSheetType::Packed: {
BestFitLayoutSamples layout;
layout.layoutSamples(
samples, m_borderPadding, m_shapePadding,
m_textureWidth, m_textureHeight);
break;
}
default: {
SimpleLayoutSamples layout(m_sheetType);
layout.layoutSamples(
samples, m_borderPadding, m_shapePadding,
m_textureWidth, m_textureHeight);
break;
}
}
}
gfx::Rect sampleBounds = it->inTextureBounds();
gfx::Size DocumentExporter::calculateSheetSize(const Samples& samples) const
{
gfx::Rect fullTextureBounds(0, 0, m_textureWidth, m_textureHeight);
for (const auto& sample : samples) {
gfx::Rect sampleBounds = sample.inTextureBounds();
// If the user specified a fixed sprite sheet size, we add the
// border padding in the sample size to do an union between
@ -589,11 +577,41 @@ Document* DocumentExporter::createEmptyTexture(const Samples& samples)
if (m_textureWidth == 0) fullTextureBounds.w += m_borderPadding;
if (m_textureHeight == 0) fullTextureBounds.h += m_borderPadding;
return gfx::Size(fullTextureBounds.x+fullTextureBounds.w,
fullTextureBounds.y+fullTextureBounds.h);
}
Document* DocumentExporter::createEmptyTexture(const Samples& samples) const
{
PixelFormat pixelFormat = IMAGE_INDEXED;
Palette* palette = nullptr;
int maxColors = 256;
for (const auto& sample : samples) {
// We try to render an indexed image. But if we find a sprite with
// two or more palettes, or two of the sprites have different
// palettes, we've to use RGB format.
if (pixelFormat == IMAGE_INDEXED) {
if (sample.sprite()->pixelFormat() != IMAGE_INDEXED) {
pixelFormat = IMAGE_RGB;
}
else if (sample.sprite()->getPalettes().size() > 1) {
pixelFormat = IMAGE_RGB;
}
else if (palette != NULL
&& palette->countDiff(sample.sprite()->palette(frame_t(0)), NULL, NULL) > 0) {
pixelFormat = IMAGE_RGB;
}
else
palette = sample.sprite()->palette(frame_t(0));
}
}
gfx::Size textureSize = calculateSheetSize(samples);
base::UniquePtr<Sprite> sprite(
Sprite::createBasicSprite(
pixelFormat,
fullTextureBounds.x+fullTextureBounds.w,
fullTextureBounds.y+fullTextureBounds.h, maxColors));
pixelFormat, textureSize.w, textureSize.h, maxColors));
if (palette != NULL)
sprite->setPalette(palette, false);
@ -604,7 +622,7 @@ Document* DocumentExporter::createEmptyTexture(const Samples& samples)
return document.release();
}
void DocumentExporter::renderTexture(const Samples& samples, Image* textureImage)
void DocumentExporter::renderTexture(const Samples& samples, Image* textureImage) const
{
textureImage->clear(0);
@ -627,7 +645,7 @@ void DocumentExporter::renderTexture(const Samples& samples, Image* textureImage
}
}
void DocumentExporter::createDataFile(const Samples& samples, std::ostream& os, Image* textureImage)
void DocumentExporter::createDataFile(const Samples& samples, std::ostream& os, Image* textureImage) const
{
std::string frames_begin;
std::string frames_end;
@ -703,7 +721,7 @@ void DocumentExporter::createDataFile(const Samples& samples, std::ostream& os,
<< " \"size\": { "
<< "\"w\": " << textureImage->width() << ", "
<< "\"h\": " << textureImage->height() << " },\n"
<< " \"scale\": \"" << m_scale << "\"";
<< " \"scale\": \"1\"";
// meta.frameTags
if (m_listFrameTags) {
@ -795,7 +813,7 @@ void DocumentExporter::createDataFile(const Samples& samples, std::ostream& os,
<< "}\n";
}
void DocumentExporter::renderSample(const Sample& sample, doc::Image* dst, int x, int y)
void DocumentExporter::renderSample(const Sample& sample, doc::Image* dst, int x, int y) const
{
render::Render render;
gfx::Clip clip(x, y, sample.trimmedBounds());

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -35,26 +35,30 @@ namespace app {
DefaultDataFormat = JsonHashDataFormat
};
enum TextureFormat {
JsonTextureFormat,
DefaultTextureFormat = JsonTextureFormat
};
enum ScaleMode {
DefaultScaleMode
};
DocumentExporter();
~DocumentExporter();
DataFormat dataFormat() const { return m_dataFormat; }
const std::string& dataFilename() { return m_dataFilename; }
const std::string& textureFilename() { return m_textureFilename; }
int textureWidth() const { return m_textureWidth; }
int textureHeight() const { return m_textureHeight; }
SpriteSheetType spriteSheetType() { return m_sheetType; }
bool ignoreEmptyCels() { return m_ignoreEmptyCels; }
int borderPadding() const { return m_borderPadding; }
int shapePadding() const { return m_shapePadding; }
int innerPadding() const { return m_innerPadding; }
bool trimCels() const { return m_trimCels; }
const std::string& filenameFormat() const { return m_filenameFormat; }
bool listFrameTags() const { return m_listFrameTags; }
bool listLayers() const { return m_listLayers; }
void setDataFormat(DataFormat format) { m_dataFormat = format; }
void setDataFilename(const std::string& filename) { m_dataFilename = filename; }
void setTextureFormat(TextureFormat format) { m_textureFormat = format; }
void setTextureFilename(const std::string& filename) { m_textureFilename = filename; }
void setTextureWidth(int width) { m_textureWidth = width; }
void setTextureHeight(int height) { m_textureHeight = height; }
void setSpriteSheetType(SpriteSheetType type) { m_sheetType = type; }
void setScale(double scale) { m_scale = scale; }
void setScaleMode(ScaleMode mode) { m_scaleMode = mode; }
void setIgnoreEmptyCels(bool ignore) { m_ignoreEmptyCels = ignore; }
void setBorderPadding(int padding) { m_borderPadding = padding; }
void setShapePadding(int padding) { m_shapePadding = padding; }
@ -72,6 +76,7 @@ namespace app {
}
Document* exportSheet();
gfx::Size calculateSheetSize();
private:
class Sample;
@ -80,11 +85,13 @@ namespace app {
class SimpleLayoutSamples;
class BestFitLayoutSamples;
void captureSamples(Samples& samples);
Document* createEmptyTexture(const Samples& samples);
void renderTexture(const Samples& samples, doc::Image* textureImage);
void createDataFile(const Samples& samples, std::ostream& os, doc::Image* textureImage);
void renderSample(const Sample& sample, doc::Image* dst, int x, int y);
void captureSamples(Samples& samples) const;
void layoutSamples(Samples& samples);
gfx::Size calculateSheetSize(const Samples& samples) const;
Document* createEmptyTexture(const Samples& samples) const;
void renderTexture(const Samples& samples, doc::Image* textureImage) const;
void createDataFile(const Samples& samples, std::ostream& os, doc::Image* textureImage) const;
void renderSample(const Sample& sample, doc::Image* dst, int x, int y) const;
class Item {
public:
@ -109,13 +116,10 @@ namespace app {
DataFormat m_dataFormat;
std::string m_dataFilename;
TextureFormat m_textureFormat;
std::string m_textureFilename;
int m_textureWidth;
int m_textureHeight;
SpriteSheetType m_sheetType;
double m_scale;
ScaleMode m_scaleMode;
bool m_ignoreEmptyCels;
int m_borderPadding;
int m_shapePadding;

View File

@ -88,7 +88,8 @@ struct ASE_Chunk {
};
static bool ase_file_read_header(FILE* f, ASE_Header* header);
static void ase_file_prepare_header(FILE* f, ASE_Header* header, const Sprite* sprite);
static void ase_file_prepare_header(FILE* f, ASE_Header* header, const Sprite* sprite,
const frame_t firstFrame, const frame_t totalFrames);
static void ase_file_write_header(FILE* f, ASE_Header* header);
static void ase_file_write_header_filesize(FILE* f, ASE_Header* header);
@ -97,7 +98,10 @@ static void ase_file_prepare_frame_header(FILE* f, ASE_FrameHeader* frame_header
static void ase_file_write_frame_header(FILE* f, ASE_FrameHeader* frame_header);
static void ase_file_write_layers(FILE* f, ASE_FrameHeader* frame_header, const Layer* layer);
static void ase_file_write_cels(FILE* f, ASE_FrameHeader* frame_header, const Sprite* sprite, const Layer* layer, frame_t frame);
static void ase_file_write_cels(FILE* f, ASE_FrameHeader* frame_header,
const Sprite* sprite, const Layer* layer,
const frame_t frame,
const frame_t firstFrame);
static void ase_file_read_padding(FILE* f, int bytes);
static void ase_file_write_padding(FILE* f, int bytes);
@ -115,13 +119,17 @@ static void ase_file_write_palette_chunk(FILE* f, ASE_FrameHeader* frame_header,
static Layer* ase_file_read_layer_chunk(FILE* f, ASE_Header* header, Sprite* sprite, Layer** previous_layer, int* current_level);
static void ase_file_write_layer_chunk(FILE* f, ASE_FrameHeader* frame_header, const Layer* layer);
static Cel* ase_file_read_cel_chunk(FILE* f, Sprite* sprite, frame_t frame, PixelFormat pixelFormat, FileOp* fop, ASE_Header* header, size_t chunk_end);
static void ase_file_write_cel_chunk(FILE* f, ASE_FrameHeader* frame_header, const Cel* cel, const LayerImage* layer, const Sprite* sprite);
static void ase_file_write_cel_chunk(FILE* f, ASE_FrameHeader* frame_header,
const Cel* cel, const LayerImage* layer,
const Sprite* sprite,
const frame_t firstFrame);
static Mask* ase_file_read_mask_chunk(FILE* f);
#if 0
static void ase_file_write_mask_chunk(FILE* f, ASE_FrameHeader* frame_header, Mask* mask);
#endif
static void ase_file_read_frame_tags_chunk(FILE* f, FrameTags* frameTags);
static void ase_file_write_frame_tags_chunk(FILE* f, ASE_FrameHeader* frame_header, const FrameTags* frameTags);
static void ase_file_write_frame_tags_chunk(FILE* f, ASE_FrameHeader* frame_header, const FrameTags* frameTags,
const frame_t fromFrame, const frame_t toFrame);
static void ase_file_read_user_data_chunk(FILE* f, UserData* userData);
static void ase_file_write_user_data_chunk(FILE* f, ASE_FrameHeader* frame_header, const UserData* userData);
@ -342,7 +350,9 @@ bool AseFormat::onSave(FileOp* fop)
// Write the header
ASE_Header header;
ase_file_prepare_header(f, &header, sprite);
ase_file_prepare_header(f, &header, sprite,
fop->roi().fromFrame(),
fop->roi().frames());
ase_file_write_header(f, &header);
bool require_new_palette_chunk = false;
@ -354,7 +364,8 @@ bool AseFormat::onSave(FileOp* fop)
}
// Write frames
for (frame_t frame(0); frame<sprite->totalFrames(); ++frame) {
for (frame_t frame=fop->roi().fromFrame();
frame <= fop->roi().toFrame(); ++frame) {
// Prepare the frame header
ASE_FrameHeader frame_header;
ase_file_prepare_frame_header(f, &frame_header);
@ -365,7 +376,9 @@ bool AseFormat::onSave(FileOp* fop)
// is the first frame or did the palette change?
Palette* pal = sprite->palette(frame);
int palFrom = 0, palTo = pal->size()-1;
if ((frame == 0 ||
if (// First frame or..
(frame == fop->roi().fromFrame() ||
// This palette is different from the previous frame palette
sprite->palette(frame-1)->countDiff(pal, &palFrom, &palTo) > 0)) {
// Write new palette chunk
if (require_new_palette_chunk) {
@ -378,7 +391,7 @@ bool AseFormat::onSave(FileOp* fop)
}
// Write extra chunks in the first frame
if (frame == 0) {
if (frame == fop->roi().fromFrame()) {
LayerIterator it = sprite->folder()->getLayerBegin();
LayerIterator end = sprite->folder()->getLayerEnd();
@ -388,18 +401,22 @@ bool AseFormat::onSave(FileOp* fop)
// Writer frame tags
if (sprite->frameTags().size() > 0)
ase_file_write_frame_tags_chunk(f, &frame_header, &sprite->frameTags());
ase_file_write_frame_tags_chunk(f, &frame_header, &sprite->frameTags(),
fop->roi().fromFrame(),
fop->roi().toFrame());
}
// Write cel chunks
ase_file_write_cels(f, &frame_header, sprite, sprite->folder(), frame);
ase_file_write_cels(f, &frame_header,
sprite, sprite->folder(),
frame, fop->roi().fromFrame());
// Write the frame header
ase_file_write_frame_header(f, &frame_header);
// Progress
if (sprite->totalFrames() > 1)
fop->setProgress(float(frame+1) / float(sprite->totalFrames()));
if (fop->roi().frames() > 1)
fop->setProgress(float(frame+1) / float(fop->roi().frames()));
if (fop->isStop())
break;
@ -457,27 +474,28 @@ static bool ase_file_read_header(FILE* f, ASE_Header* header)
return true;
}
static void ase_file_prepare_header(FILE* f, ASE_Header* header, const Sprite* sprite)
static void ase_file_prepare_header(FILE* f, ASE_Header* header, const Sprite* sprite,
const frame_t firstFrame, const frame_t totalFrames)
{
header->pos = ftell(f);
header->size = 0;
header->magic = ASE_FILE_MAGIC;
header->frames = sprite->totalFrames();
header->frames = totalFrames;
header->width = sprite->width();
header->height = sprite->height();
header->depth = (sprite->pixelFormat() == IMAGE_RGB ? 32:
sprite->pixelFormat() == IMAGE_GRAYSCALE ? 16:
sprite->pixelFormat() == IMAGE_INDEXED ? 8: 0);
header->flags = ASE_FILE_FLAG_LAYER_WITH_OPACITY;
header->speed = sprite->frameDuration(frame_t(0));
header->speed = sprite->frameDuration(firstFrame);
header->next = 0;
header->frit = 0;
header->transparent_index = sprite->transparentColor();
header->ignore[0] = 0;
header->ignore[1] = 0;
header->ignore[2] = 0;
header->ncolors = sprite->palette(frame_t(0))->size();
header->ncolors = sprite->palette(firstFrame)->size();
header->pixel_width = sprite->pixelRatio().w;
header->pixel_height = sprite->pixelRatio().h;
}
@ -571,7 +589,10 @@ static void ase_file_write_layers(FILE* f, ASE_FrameHeader* frame_header, const
}
}
static void ase_file_write_cels(FILE* f, ASE_FrameHeader* frame_header, const Sprite* sprite, const Layer* layer, frame_t frame)
static void ase_file_write_cels(FILE* f, ASE_FrameHeader* frame_header,
const Sprite* sprite, const Layer* layer,
const frame_t frame,
const frame_t firstFrame)
{
if (layer->isImage()) {
const Cel* cel = layer->cel(frame);
@ -579,7 +600,9 @@ static void ase_file_write_cels(FILE* f, ASE_FrameHeader* frame_header, const Sp
/* fop->setError("New cel in frame %d, in layer %d\n", */
/* frame, sprite_layer2index(sprite, layer)); */
ase_file_write_cel_chunk(f, frame_header, cel, static_cast<const LayerImage*>(layer), sprite);
ase_file_write_cel_chunk(f, frame_header, cel,
static_cast<const LayerImage*>(layer),
sprite, firstFrame);
if (!cel->link() &&
!cel->data()->userData().isEmpty()) {
@ -594,7 +617,7 @@ static void ase_file_write_cels(FILE* f, ASE_FrameHeader* frame_header, const Sp
end = static_cast<const LayerFolder*>(layer)->getLayerEnd();
for (; it != end; ++it)
ase_file_write_cels(f, frame_header, sprite, *it, frame);
ase_file_write_cels(f, frame_header, sprite, *it, frame, firstFrame);
}
}
@ -1269,12 +1292,28 @@ static Cel* ase_file_read_cel_chunk(FILE* f, Sprite* sprite, frame_t frame,
}
static void ase_file_write_cel_chunk(FILE* f, ASE_FrameHeader* frame_header,
const Cel* cel, const LayerImage* layer, const Sprite* sprite)
const Cel* cel, const LayerImage* layer,
const Sprite* sprite,
const frame_t firstFrame)
{
ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_CEL);
int layer_index = sprite->layerToIndex(layer);
const Cel* link = cel->link();
// In case the original link is outside the ROI, we've to find the
// first linked cel that is inside the ROI.
if (link && link->frame() < firstFrame) {
link = nullptr;
for (frame_t i=firstFrame; i<=cel->frame(); ++i) {
link = layer->cel(i);
if (link && link->image()->id() == cel->image()->id())
break;
}
if (link == cel)
link = nullptr;
}
int cel_type = (link ? ASE_FILE_LINK_CEL: ASE_FILE_COMPRESSED_CEL);
fputw(layer_index, f);
@ -1320,7 +1359,7 @@ static void ase_file_write_cel_chunk(FILE* f, ASE_FrameHeader* frame_header,
case ASE_FILE_LINK_CEL:
// Linked cel to another frame
fputw(link->frame(), f);
fputw(link->frame()-firstFrame, f);
break;
case ASE_FILE_COMPRESSED_CEL: {
@ -1449,18 +1488,34 @@ static void ase_file_read_frame_tags_chunk(FILE* f, FrameTags* frameTags)
}
}
static void ase_file_write_frame_tags_chunk(FILE* f, ASE_FrameHeader* frame_header, const FrameTags* frameTags)
static void ase_file_write_frame_tags_chunk(FILE* f, ASE_FrameHeader* frame_header, const FrameTags* frameTags,
const frame_t fromFrame, const frame_t toFrame)
{
ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_FRAME_TAGS);
fputw(frameTags->size(), f);
int tags = 0;
for (const FrameTag* tag : *frameTags) {
// Skip tags that are outside the given ROI
if (tag->fromFrame() > toFrame ||
tag->toFrame() < fromFrame)
continue;
++tags;
}
fputw(tags, f);
fputl(0, f); // 8 reserved bytes
fputl(0, f);
for (const FrameTag* tag : *frameTags) {
fputw(tag->fromFrame(), f);
fputw(tag->toFrame(), f);
if (tag->fromFrame() > toFrame ||
tag->toFrame() < fromFrame)
continue;
frame_t from = MID(0, tag->fromFrame()-fromFrame, toFrame-fromFrame);
frame_t to = MID(from, tag->toFrame()-fromFrame, toFrame-fromFrame);
fputw(from, f);
fputw(to, f);
fputc((int)tag->aniDir(), f);
fputl(0, f); // 8 reserved bytes

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -693,7 +693,7 @@ bool BmpFormat::onLoad(FileOp *fop)
}
// Setup the file-data.
if (!fop->sequenceGetFormatOptions()) {
if (!fop->formatOptions()) {
base::SharedPtr<BmpOptions> bmp_options(new BmpOptions());
bmp_options->format = format;
@ -703,7 +703,7 @@ bool BmpFormat::onLoad(FileOp *fop)
bmp_options->green_mask = gmask;
bmp_options->blue_mask = bmask;
fop->sequenceSetFormatOptions(bmp_options);
fop->setFormatOptions(bmp_options);
}
return true;

View File

@ -103,7 +103,7 @@ int save_document(Context* context, doc::Document* document)
UniquePtr<FileOp> fop(
FileOp::createSaveDocumentOperation(
context,
static_cast<app::Document*>(document),
FileOpROI(static_cast<app::Document*>(document), "", -1, -1),
document->filename().c_str(), ""));
if (!fop)
return -1;
@ -120,6 +120,66 @@ int save_document(Context* context, doc::Document* document)
return (!fop->hasError() ? 0: -1);
}
bool is_static_image_format(const std::string& filename)
{
std::string extension =
base::string_to_lower(
base::get_file_extension(filename));
// Get the format through the extension of the filename
FileFormat* format = FileFormatsManager::instance()
->getFileFormatByExtension(extension.c_str());
return (format && format->support(FILE_SUPPORT_SEQUENCES));
}
FileOpROI::FileOpROI()
: m_document(nullptr)
, m_frameTag(nullptr)
, m_fromFrame(-1)
, m_toFrame(-1)
{
}
FileOpROI::FileOpROI(const app::Document* doc,
const std::string& frameTagName,
const doc::frame_t fromFrame,
const doc::frame_t toFrame)
: m_document(doc)
, m_frameTag(nullptr)
, m_fromFrame(fromFrame)
, m_toFrame(toFrame)
{
if (doc) {
if (fromFrame >= 0)
m_fromFrame = MID(0, fromFrame, doc->sprite()->lastFrame());
else
m_fromFrame = 0;
if (toFrame >= 0)
m_toFrame = MID(m_fromFrame, toFrame, doc->sprite()->lastFrame());
else
m_toFrame = doc->sprite()->lastFrame();
if (!frameTagName.empty()) {
doc::FrameTag* tag = doc->sprite()->frameTags().getByName(frameTagName);
if (tag) {
m_frameTag = tag;
if (fromFrame >= 0)
m_fromFrame = tag->fromFrame() + MID(0, fromFrame, tag->frames()-1);
else
m_fromFrame = tag->fromFrame();
if (toFrame >= 0)
m_toFrame = tag->fromFrame() + MID(fromFrame, toFrame, tag->frames()-1);
else
m_toFrame = tag->toFrame();
}
}
}
}
// static
FileOp* FileOp::createLoadDocumentOperation(Context* context, const char* filename, int flags)
{
@ -163,8 +223,8 @@ FileOp* FileOp::createLoadDocumentOperation(Context* context, const char* filena
int c, width, start_from;
char buf[512];
/* first of all, we must generate the list of files to load in the
sequence... */
// 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);
@ -218,15 +278,16 @@ done:;
// static
FileOp* FileOp::createSaveDocumentOperation(const Context* context,
const Document* document,
const FileOpROI& roi,
const char* filename,
const char* fn_format_arg)
const char* filenameFormatArg)
{
base::UniquePtr<FileOp> fop(
new FileOp(FileOpSave, const_cast<Context*>(context)));
// Document to save
fop->m_document = const_cast<Document*>(document);
fop->m_document = const_cast<Document*>(roi.document());
fop->m_roi = roi;
// Get the extension of the filename (in lower case)
std::string extension = base::string_to_lower(base::get_file_extension(filename));
@ -247,7 +308,7 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context,
std::string warnings;
bool fatal = false;
/* check image type support */
// Check image type support
switch (fop->m_document->sprite()->pixelFormat()) {
case IMAGE_RGB:
@ -284,7 +345,7 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context,
}
// Frames support
if (fop->m_document->sprite()->totalFrames() > 1) {
if (fop->m_roi.frames() > 1) {
if (!fop->m_format->support(FILE_SUPPORT_FRAMES) &&
!fop->m_format->support(FILE_SUPPORT_SEQUENCES)) {
warnings += "<<- Frames";
@ -381,81 +442,45 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context,
fop->prepareForSequence();
std::string fn = filename;
std::string fn_format = fn_format_arg;
bool default_format = false;
std::string fn_format = filenameFormatArg;
if (fn_format.empty()) {
if (fop->m_document->sprite()->totalFrames() == 1)
fn_format = "{fullname}";
else {
fn_format = "{path}/{title}{frame}.{extension}";
default_format = true;
}
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
}
// Save one frame
if (fop->m_document->sprite()->totalFrames() == 1) {
Sprite* spr = fop->m_document->sprite();
for (frame_t frame = fop->m_roi.fromFrame();
frame <= fop->m_roi.toFrame(); ++frame) {
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);
fnInfo
.filename(fn)
.innerTagName(innerTag ? innerTag->name(): "")
.outerTagName(outerTag ? outerTag->name(): "")
.frame(frame - fop->m_roi.fromFrame())
.tagFrame(innerTag ? frame - innerTag->fromFrame():
frame);
fn = filename_formatter(fn_format, fnInfo);
fop->m_seq.filename_list.push_back(fn);
fop->m_seq.filename_list.push_back(
filename_formatter(fn_format, fnInfo));
}
// Save multiple frames
else {
int width = 0;
int start_from = 0;
if (default_format) {
std::string left, right;
start_from = split_filename(fn.c_str(), left, right, width);
if (start_from < 0) {
start_from = 1;
width = 1;
}
else {
fn = left;
fn += right;
}
}
Sprite* spr = fop->m_document->sprite();
std::vector<char> buf(32);
std::sprintf(&buf[0], "{frame%0*d}", width, 0);
if (default_format)
fn_format = set_frame_format(fn_format, &buf[0]);
else if (spr->totalFrames() > 1)
fn_format = add_frame_format(fn_format, &buf[0]);
for (frame_t frame(0); frame<spr->totalFrames(); ++frame) {
FrameTag* innerTag = spr->frameTags().innerTag(frame);
FrameTag* outerTag = spr->frameTags().outerTag(frame);
FilenameInfo fnInfo;
fnInfo
.filename(fn)
.innerTagName(innerTag ? innerTag->name(): "")
.outerTagName(outerTag ? outerTag->name(): "")
.frame(start_from+frame)
.tagFrame(innerTag ? frame-innerTag->fromFrame():
start_from+frame);
std::string frame_fn =
filename_formatter(fn_format, fnInfo);
fop->m_seq.filename_list.push_back(frame_fn);
}
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;
}
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
@ -463,15 +488,15 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context,
// Configure output format?
if (fop->m_format->support(FILE_SUPPORT_GET_FORMAT_OPTIONS)) {
base::SharedPtr<FormatOptions> format_options =
base::SharedPtr<FormatOptions> opts =
fop->m_format->getFormatOptions(fop);
// Does the user cancelled the operation?
if (!format_options)
if (!opts)
return nullptr;
fop->m_seq.format_options = format_options;
fop->m_document->setFormatOptions(format_options);
fop->m_formatOptions = opts;
fop->m_document->setFormatOptions(opts);
}
return fop.release();
@ -598,7 +623,7 @@ void FileOp::operate(IFileOpProgress* progress)
// Sets special options from the specific format (e.g. BMP
// file can contain the number of bits per pixel).
m_document->setFormatOptions(m_seq.format_options);
m_document->setFormatOptions(m_formatOptions);
}
}
// Direct load from one file.
@ -630,7 +655,8 @@ void FileOp::operate(IFileOpProgress* progress)
// For each frame in the sprite.
render::Render render;
for (frame_t frame(0); frame < sprite->totalFrames(); ++frame) {
for (frame_t frame = m_roi.fromFrame();
frame <= m_roi.toFrame(); ++frame) {
// Draw the "frame" in "m_seq.image"
render.renderSprite(m_seq.image.get(), sprite, frame);
@ -638,7 +664,7 @@ void FileOp::operate(IFileOpProgress* progress)
sprite->palette(frame)->copyColorsTo(m_seq.palette);
// Setup the filename to be used.
m_filename = m_seq.filename_list[frame];
m_filename = m_seq.filename_list[frame - m_roi.fromFrame()];
// Call the "save" procedure... did it fail?
if (!m_format->save(this)) {
@ -745,15 +771,15 @@ void FileOp::postLoad()
m_document->markAsSaved();
}
base::SharedPtr<FormatOptions> FileOp::sequenceGetFormatOptions() const
base::SharedPtr<FormatOptions> FileOp::formatOptions() const
{
return m_seq.format_options;
return m_formatOptions;
}
void FileOp::sequenceSetFormatOptions(const base::SharedPtr<FormatOptions>& format_options)
void FileOp::setFormatOptions(const base::SharedPtr<FormatOptions>& opts)
{
ASSERT(!m_seq.format_options);
m_seq.format_options = format_options;
ASSERT(!m_formatOptions);
m_formatOptions = opts;
}
void FileOp::sequenceSetNColors(int ncolors)
@ -878,6 +904,16 @@ void FileOp::setProgress(double progress)
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);
}
}
double FileOp::progress() const
{
double progress;
@ -933,7 +969,7 @@ FileOp::FileOp(FileOpType type, Context* context)
void FileOp::prepareForSequence()
{
m_seq.palette = new Palette(frame_t(0), 256);
m_seq.format_options.reset();
m_formatOptions.reset();
}
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -15,10 +15,11 @@
#include "doc/image_ref.h"
#include "doc/pixel_format.h"
#include <stdio.h>
#include <cstdio>
#include <string>
#include <vector>
// Flags for FileOp::createLoadDocumentOperation()
#define FILE_LOAD_SEQUENCE_NONE 0x00000001
#define FILE_LOAD_SEQUENCE_ASK 0x00000002
#define FILE_LOAD_SEQUENCE_YES 0x00000004
@ -26,6 +27,7 @@
namespace doc {
class Document;
class FrameTag;
}
namespace doc {
@ -51,18 +53,49 @@ namespace app {
FileOpSave
} FileOpType;
class IFileOpProgress
{
class IFileOpProgress {
public:
virtual ~IFileOpProgress() { }
virtual void ackFileOpProgress(double progress) = 0;
};
class FileOpROI { // Region of interest
public:
FileOpROI();
FileOpROI(const app::Document* doc,
const std::string& frameTagName,
const doc::frame_t fromFrame,
const doc::frame_t toFrame);
const app::Document* document() const { return m_document; }
doc::FrameTag* frameTag() const { return m_frameTag; }
doc::frame_t fromFrame() const { return m_fromFrame; }
doc::frame_t toFrame() const { return m_toFrame; }
doc::frame_t frames() const {
ASSERT(m_fromFrame >= 0);
ASSERT(m_toFrame >= 0);
return (m_toFrame - m_fromFrame + 1);
}
private:
const app::Document* m_document;
doc::FrameTag* m_frameTag;
doc::frame_t m_fromFrame;
doc::frame_t m_toFrame;
};
// Structure to load & save files.
class FileOp {
public:
static FileOp* createLoadDocumentOperation(Context* context, const char* filename, int flags);
static FileOp* createSaveDocumentOperation(const Context* context, const Document* document, const char* filename, const char* fn_format);
static FileOp* createLoadDocumentOperation(Context* context,
const char* filename,
int flags);
static FileOp* createSaveDocumentOperation(const Context* context,
const FileOpROI& roi,
const char* filename,
const char* filenameFormat);
~FileOp();
@ -78,6 +111,8 @@ namespace app {
return doc;
}
const FileOpROI& roi() const { return m_roi; }
void createDocument(Sprite* spr);
void operate(IFileOpProgress* progress = nullptr);
@ -89,10 +124,12 @@ namespace app {
// Does extra post-load processing which may require user intervention.
void postLoad();
// Special options specific to the file format.
base::SharedPtr<FormatOptions> formatOptions() const;
void setFormatOptions(const base::SharedPtr<FormatOptions>& opts);
// Helpers for file decoder/encoder (FileFormat) with
// FILE_SUPPORT_SEQUENCES flag.
base::SharedPtr<FormatOptions> sequenceGetFormatOptions() const;
void sequenceSetFormatOptions(const base::SharedPtr<FormatOptions>& formatOptions);
void sequenceSetNColors(int ncolors);
int sequenceGetNColors() const;
void sequenceSetColor(int index, int r, int g, int b);
@ -115,6 +152,8 @@ namespace app {
double progress() const;
void setProgress(double progress);
void getFilenameList(std::vector<std::string>& output) const;
private:
FileOp(); // Undefined
FileOp(FileOpType type, Context* context);
@ -126,6 +165,7 @@ namespace app {
// releaseDocument() member function)
Document* m_document; // Loaded document, or document to be saved.
std::string m_filename; // File-name to load/save.
FileOpROI m_roi;
// Shared fields between threads.
mutable base::mutex m_mutex; // Mutex to access to the next two fields.
@ -138,6 +178,8 @@ namespace app {
// that support animation like
// GIF/FLI/ASE).
base::SharedPtr<FormatOptions> m_formatOptions;
// Data for sequences.
struct {
std::vector<std::string> filename_list; // All file names to load/save.
@ -151,22 +193,24 @@ namespace app {
bool has_alpha;
LayerImage* layer;
Cel* last_cel;
base::SharedPtr<FormatOptions> format_options;
} m_seq;
void prepareForSequence();
};
// Available extensions for each load/save operation.
std::string get_readable_extensions();
std::string get_writable_extensions();
// High-level routines to load/save documents.
app::Document* load_document(Context* context, const char* filename);
int save_document(Context* context, doc::Document* document);
// Returns true if the given filename contains a file extension that
// can be used to save only static images (i.e. animations are saved
// as sequence of files).
bool is_static_image_format(const std::string& filename);
} // namespace app
#endif

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -40,7 +40,7 @@ static FileFormatsManager* singleton = NULL;
FileFormatsManager* FileFormatsManager::instance()
{
if (!singleton)
singleton = new FileFormatsManager();
singleton = new FileFormatsManager;
return singleton;
}
@ -51,15 +51,7 @@ void FileFormatsManager::destroyInstance()
singleton = NULL;
}
FileFormatsManager::~FileFormatsManager()
{
FileFormatsList::iterator end = this->end();
for (FileFormatsList::iterator it = begin(); it != end; ++it) {
delete (*it); // delete the FileFormat
}
}
void FileFormatsManager::registerAllFormats()
FileFormatsManager::FileFormatsManager()
{
// The first format is the default image format in FileSelector
registerFormat(CreateAseFormat());
@ -77,6 +69,14 @@ void FileFormatsManager::registerAllFormats()
#endif
}
FileFormatsManager::~FileFormatsManager()
{
FileFormatsList::iterator end = this->end();
for (FileFormatsList::iterator it = begin(); it != end; ++it) {
delete (*it); // delete the FileFormat
}
}
void FileFormatsManager::registerFormat(FileFormat* fileFormat)
{
m_formats.push_back(fileFormat);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -29,8 +29,6 @@ namespace app {
virtual ~FileFormatsManager();
void registerAllFormats();
// Iterators to access to the list of formats.
FileFormatsList::iterator begin();
FileFormatsList::iterator end();
@ -38,7 +36,7 @@ namespace app {
FileFormat* getFileFormatByExtension(const char* extension) const;
private:
// Register one format.
FileFormatsManager();
void registerFormat(FileFormat* fileFormat);
FileFormatsList m_formats;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -12,6 +12,7 @@
#include "app/document.h"
#include "app/file/file.h"
#include "app/file/file_formats_manager.h"
#include "base/unique_ptr.h"
#include "doc/doc.h"
#include <cstdio>
@ -23,7 +24,6 @@ using namespace app;
TEST(File, SeveralSizes)
{
// Register all possible image formats.
FileFormatsManager::instance()->registerAllFormats();
std::vector<char> fn(256);
app::Context ctx;
@ -33,7 +33,7 @@ TEST(File, SeveralSizes)
std::sprintf(&fn[0], "test.ase");
{
doc::Document* doc = ctx.documents().add(w, h, doc::ColorMode::INDEXED, 256);
base::UniquePtr<doc::Document> doc(ctx.documents().add(w, h, doc::ColorMode::INDEXED, 256));
doc->setFilename(&fn[0]);
// Random pixels
@ -50,19 +50,18 @@ TEST(File, SeveralSizes)
}
}
save_document(&ctx, doc);
save_document(&ctx, doc.get());
doc->close();
delete doc;
}
{
app::Document* doc = load_document(&ctx, &fn[0]);
base::UniquePtr<app::Document> doc(load_document(&ctx, &fn[0]));
ASSERT_EQ(w, doc->sprite()->width());
ASSERT_EQ(h, doc->sprite()->height());
// Same random pixels (see the seed)
Layer* layer = doc->sprite()->folder()->getFirstLayer();
ASSERT_TRUE(layer != NULL);
ASSERT_TRUE(layer != nullptr);
Image* image = layer->cel(frame_t(0))->image();
std::srand(w*h);
int c = std::rand()%256;
@ -75,7 +74,6 @@ TEST(File, SeveralSizes)
}
doc->close();
delete doc;
}
}
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -156,11 +156,13 @@ bool FliFormat::onLoad(FileOp* fop)
#ifdef ENABLE_SAVE
static int get_time_precision(const Sprite *sprite)
static int get_time_precision(const Sprite *sprite,
const doc::frame_t fromFrame,
const doc::frame_t toFrame)
{
// Check if all frames have the same duration
bool constantFrameRate = true;
for (frame_t c(1); c < sprite->totalFrames(); ++c) {
for (frame_t c=fromFrame+1; c <= toFrame; ++c) {
if (sprite->frameDuration(c-1) != sprite->frameDuration(c)) {
constantFrameRate = false;
break;
@ -170,7 +172,7 @@ static int get_time_precision(const Sprite *sprite)
return sprite->frameDuration(0);
int precision = 1000;
for (frame_t c(0); c < sprite->totalFrames() && precision > 1; ++c) {
for (frame_t c=fromFrame; c <= toFrame && precision > 1; ++c) {
int len = sprite->frameDuration(c);
while (len / precision == 0)
precision /= 10;
@ -192,7 +194,10 @@ bool FliFormat::onSave(FileOp* fop)
header.frames = 0;
header.width = sprite->width();
header.height = sprite->height();
header.speed = get_time_precision(sprite);
header.speed = get_time_precision(
sprite,
fop->roi().fromFrame(),
fop->roi().toFrame());
encoder.writeHeader(header);
// Create the bitmaps
@ -203,10 +208,9 @@ bool FliFormat::onSave(FileOp* fop)
flic::Frame fliFrame;
fliFrame.pixels = bmp->getPixelAddress(0, 0);
fliFrame.rowstride = IndexedTraits::getRowStrideBytes(bmp->width());
for (frame_t frame_it=0;
frame_it <= sprite->totalFrames();
++frame_it) {
frame_t frame = (frame_it % sprite->totalFrames());
frame_t nframes = fop->roi().frames();
for (frame_t frame_it=0; frame_it <= nframes; ++frame_it) {
frame_t frame = fop->roi().fromFrame() + (frame_it % nframes);
const Palette* pal = sprite->palette(frame);
int size = MIN(256, pal->size());
@ -222,7 +226,7 @@ bool FliFormat::onSave(FileOp* fop)
// How many times this frame should be written to get the same
// time that it has in the sprite
if (frame_it < sprite->totalFrames()) {
if (frame_it < nframes) {
int times = sprite->frameDuration(frame) / header.speed;
times = MAX(1, times);
for (int c=0; c<times; c++)
@ -233,7 +237,7 @@ bool FliFormat::onSave(FileOp* fop)
}
// Update progress
fop->setProgress((float)(frame_it+1) / (float)(sprite->totalFrames()+1));
fop->setProgress((float)(frame_it+1) / (float)(nframes+1));
}
return true;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -870,7 +870,7 @@ public:
else
m_clearColor = rgba(0, 0, 0, 0);
const base::SharedPtr<GifOptions> gifOptions = fop->sequenceGetFormatOptions();
const base::SharedPtr<GifOptions> gifOptions = fop->formatOptions();
m_interlaced = gifOptions->interlaced();
m_loop = (gifOptions->loop() ? 0: -1);
@ -897,7 +897,7 @@ public:
m_currentImage = m_images[1].get();
m_nextImage = m_images[2].get();
int nframes = m_sprite->totalFrames();
int nframes = totalFrames();
for (int frameNum=0; frameNum<nframes; ++frameNum) {
if (frameNum == 0)
renderFrame(0, m_nextImage);
@ -933,6 +933,14 @@ public:
private:
doc::frame_t totalFrames() const {
return m_fop->roi().frames();
}
doc::frame_t fromFrame() const {
return m_fop->roi().fromFrame();
}
void writeHeader() {
if (EGifPutScreenDesc(m_gifFile,
m_spriteBounds.w,
@ -983,7 +991,7 @@ private:
// frame and maybe the transparency index).
void writeExtension(int frameNum, int transparentIndex, DisposalMethod disposalMethod) {
unsigned char extension_bytes[5];
int frameDelay = m_sprite->frameDuration(frameNum) / 10;
int frameDelay = m_sprite->frameDuration(fromFrame()+frameNum) / 10;
extension_bytes[0] = (((int(disposalMethod) & 7) << 2) |
(transparentIndex >= 0 ? 1: 0));
@ -1029,7 +1037,7 @@ private:
prev = calculateFrameBounds(m_currentImage, m_previousImage);
if (!m_hasBackground &&
frameNum+1 < m_sprite->totalFrames())
frameNum+1 < totalFrames())
next = calculateFrameBounds(m_currentImage, m_nextImage);
frameBounds = prev.createUnion(next);
@ -1056,8 +1064,8 @@ private:
void writeImage(int frameNum, const gfx::Rect& frameBounds, DisposalMethod disposal) {
UniquePtr<Palette> framePaletteRef;
UniquePtr<RgbMap> rgbmapRef;
Palette* framePalette = m_sprite->palette(frameNum);
RgbMap* rgbmap = m_sprite->rgbMap(frameNum);
Palette* framePalette = m_sprite->palette(fromFrame()+frameNum);
RgbMap* rgbmap = m_sprite->rgbMap(fromFrame()+frameNum);
// Create optimized palette for RGB/Grayscale images
if (m_quantizeColormaps) {
@ -1235,7 +1243,7 @@ private:
render::Render render;
render.setBgType(render::BgType::NONE);
clear_image(dst, m_clearColor);
render.renderSprite(dst, m_sprite, frameNum);
render.renderSprite(dst, m_sprite, fromFrame()+frameNum);
}
private:

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -237,8 +237,7 @@ bool JpegFormat::onSave(FileOp* fop)
const Image* image = fop->sequenceImage();
JSAMPARRAY buffer;
JDIMENSION buffer_height;
const base::SharedPtr<JpegOptions> jpeg_options =
fop->sequenceGetFormatOptions();
const base::SharedPtr<JpegOptions> jpeg_options = fop->formatOptions();
int c;
// Open the file for write in it.

View File

@ -1,6 +1,6 @@
// Aseprite
// Copyright (C) 2015 Gabriel Rauter
// Copyright (C) 2015 David Capello
// Copyright (C) 2015-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -154,7 +154,7 @@ bool WebPFormat::onLoad(FileOp* fop)
break;
}
if (!fop->sequenceGetFormatOptions()) {
if (!fop->formatOptions()) {
base::SharedPtr<WebPOptions> webPOptions(new WebPOptions());
webPOptions->setLossless(std::min(config.input.format - 1, 1));
fop->sequenceSetFormatOptions(webPOptions);
@ -251,8 +251,7 @@ bool WebPFormat::onSave(FileOp* fop)
return false;
}
base::SharedPtr<WebPOptions> webp_options =
fop->sequenceGetFormatOptions();
base::SharedPtr<WebPOptions> webp_options = fop->formatOptions();
WebPConfig config;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -11,6 +11,8 @@
#include "app/filename_formatter.h"
#include "app/file/file.h"
#include "app/file/split_filename.h"
#include "base/convert_to.h"
#include "base/path.h"
#include "base/replace_string.h"
@ -48,10 +50,34 @@ static bool replace_frame(const char* frameKey, // E.g. = "{frame"
return false;
}
bool get_frame_info_from_filename_format(
const std::string& format, int* frameBase, int* width)
{
const char* frameKey = "{frame";
size_t i = format.find(frameKey);
if (i != std::string::npos) {
int keyLen = std::strlen(frameKey);
size_t j = format.find("}", i+keyLen);
if (j != std::string::npos) {
std::string frameStr = format.substr(i, j - i + 1);
if (frameBase)
*frameBase = std::strtol(frameStr.c_str()+keyLen, NULL, 10);
if (width)
*width = (int(j) - int(i+keyLen));
}
return true;
}
else
return false;
}
std::string filename_formatter(
const std::string& format,
FilenameInfo& info,
bool replaceFrame)
const bool replaceFrame)
{
const std::string& filename = info.filename();
std::string path = base::get_file_path(filename);
@ -77,42 +103,95 @@ std::string filename_formatter(
return output;
}
std::string set_frame_format(
const std::string& format,
const std::string& newFrameFormat)
std::string get_default_filename_format(
std::string& filename,
const bool withPath,
const bool hasFrames,
const bool hasLayer,
const bool hasFrameTag)
{
std::string output = format;
std::string format;
size_t i = output.find("{frame");
if (i != std::string::npos) {
size_t j = output.find("}", i+6);
if (j != std::string::npos) {
output.replace(i, j - i + 1, newFrameFormat);
if (withPath)
format += "{path}/";
format += "{title}";
if (hasLayer)
format += " ({layer})";
if (hasFrameTag)
format += " #{tag}";
if (hasFrames && is_static_image_format(filename) &&
filename.find("{frame") == std::string::npos &&
filename.find("{tagframe") == std::string::npos) {
const bool autoFrameFromLastDigit =
(!hasLayer &&
!hasFrameTag);
// Check if we already have a frame number at the end of the
// filename (e.g. output01.png)
int frameBase = -1, frameWidth = 0;
std::string left, right;
if (autoFrameFromLastDigit)
frameBase = split_filename(filename.c_str(), left, right, frameWidth);
if (frameBase >= 0) {
std::vector<char> buf(32);
std::sprintf(&buf[0], "{frame%0*d}", frameWidth, frameBase);
if (hasLayer || hasFrameTag)
format += " ";
format += &buf[0];
// Remove the frame number from the filename part.
filename = left;
filename += right;
}
// Check if there is already a {frame} tag in the filename
else if (get_frame_info_from_filename_format(filename, &frameBase, &frameWidth)) {
// Do nothing
}
else {
if (hasLayer || hasFrameTag)
format += " {frame}";
else
format += "{frame1}";
}
}
return output;
format += ".{extension}";
return format;
}
std::string add_frame_format(
const std::string& format,
const std::string& newFrameFormat)
std::string get_default_filename_format_for_sheet(
const std::string& filename,
const bool hasFrames,
const bool hasLayer,
const bool hasFrameTag)
{
std::string output = format;
std::string format = "{title}";
size_t i = output.find("{frame");
size_t j = output.find("{tagframe");
if (i == std::string::npos &&
j == std::string::npos) {
output =
base::join_path(
base::get_file_path(format),
base::get_file_title(format))
+ newFrameFormat + "." +
base::get_file_extension(format);
if (hasLayer)
format += " ({layer})";
if (hasFrameTag)
format += " #{tag}";
if (hasFrames) {
int frameBase, frameWidth;
// Check if there is already a {frame} tag in the filename
if (get_frame_info_from_filename_format(filename, &frameBase, &frameWidth)) {
// Do nothing
}
else {
format += " {frame}";
}
}
return output;
format += ".{extension}";
return format;
}
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -63,21 +63,31 @@ namespace app {
int m_tagFrame;
};
// Returns the information inside {frame} tag.
// E.g. For {frame001} returns width=3 and startFrom=1
bool get_frame_info_from_filename_format(
const std::string& format, int* startFrom, int* width);
// If "replaceFrame" is false, this function doesn't replace all the
// information that depends on the current frame ({frame},
// {tagframe}, {tag}, etc.)
std::string filename_formatter(
const std::string& format,
FilenameInfo& info,
bool replaceFrame = true);
const bool replaceFrame = true);
std::string set_frame_format(
const std::string& format,
const std::string& newFrameFormat);
std::string get_default_filename_format(
std::string& filename,
const bool withPath,
const bool hasFrames,
const bool hasLayer,
const bool hasFrameTag);
std::string add_frame_format(
const std::string& format,
const std::string& newFrameFormat);
std::string get_default_filename_format_for_sheet(
const std::string& filename,
const bool hasFrames,
const bool hasLayer,
const bool hasFrameTag);
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -130,42 +130,6 @@ TEST(FilenameFormatter, WithFrame)
FilenameInfo().filename("C:/temp/file.png").layerName("Background").frame(2)));
}
TEST(SetFrameFormat, Tests)
{
EXPECT_EQ(
"{path}/{title}{frame1}.{extension}",
set_frame_format(
"{path}/{title}{frame}.{extension}",
"{frame1}"));
EXPECT_EQ(
"{path}/{title}{frame01}.{extension}",
set_frame_format(
"{path}/{title}{frame}.{extension}",
"{frame01}"));
EXPECT_EQ(
"{path}/{title}{frame}.{extension}",
set_frame_format(
"{path}/{title}{frame01}.{extension}",
"{frame}"));
}
TEST(AddFrameFormat, Tests)
{
EXPECT_EQ(
base::fix_path_separators("{path}/{title}{frame001}.{extension}"),
add_frame_format(
"{path}/{title}.{extension}",
"{frame001}"));
EXPECT_EQ(
"{path}/{title}{frame1}.{extension}",
add_frame_format(
"{path}/{title}{frame1}.{extension}",
"{frame001}"));
}
TEST(FilenameFormatter, WithTagFrame)
{
EXPECT_EQ(
@ -186,3 +150,71 @@ TEST(FilenameFormatter, WithTagFrame)
"{path}/{title}_{frame}_{tagframe24}.{extension}",
FilenameInfo().filename("./file.png").frame(2).tagFrame(1)));
}
TEST(FilenameFormatter, GetFrameInfo)
{
int frameBase, width;
EXPECT_EQ(false, get_frame_info_from_filename_format("hi.png", nullptr, nullptr));
frameBase = width = -1;
EXPECT_EQ(true, get_frame_info_from_filename_format("hi{frame}.png", &frameBase, &width));
EXPECT_EQ(0, frameBase);
EXPECT_EQ(0, width);
frameBase = width = -1;
EXPECT_EQ(true, get_frame_info_from_filename_format("hi{frame1}.png", &frameBase, &width));
EXPECT_EQ(1, frameBase);
EXPECT_EQ(1, width);
frameBase = width = -1;
EXPECT_EQ(true, get_frame_info_from_filename_format("hi{frame032}.png", &frameBase, &width));
EXPECT_EQ(32, frameBase);
EXPECT_EQ(3, width);
}
TEST(FilenameFormatter, DefaultFormat)
{
std::string fn;
fn = "/path/hello.png";
EXPECT_EQ("{title}.{extension}", get_default_filename_format(fn, false, false, false, false));
EXPECT_EQ("/path/hello.png", fn);
fn = "/path/hello.png";
EXPECT_EQ("{path}/{title}.{extension}", get_default_filename_format(fn, true, false, false, false));
EXPECT_EQ("/path/hello.png", fn);
fn = "/path/hello.png";
EXPECT_EQ("{path}/{title}{frame1}.{extension}", get_default_filename_format(fn, true, true, false, false));
EXPECT_EQ("/path/hello.png", fn);
fn = "/path/hello.gif";
EXPECT_EQ("{path}/{title}.{extension}", get_default_filename_format(fn, true, true, false, false));
EXPECT_EQ("/path/hello.gif", fn);
fn = "/path/hello.png";
EXPECT_EQ("{path}/{title} ({layer}) {frame}.{extension}", get_default_filename_format(fn, true, true, true, false));
EXPECT_EQ("/path/hello.png", fn);
fn = "/path/hello.gif";
EXPECT_EQ("{path}/{title} ({layer}).{extension}", get_default_filename_format(fn, true, true, true, false));
EXPECT_EQ("/path/hello.gif", fn);
fn = "/path/hello1.png";
EXPECT_EQ("{path}/{title}{frame1}.{extension}", get_default_filename_format(fn, true, true, false, false));
EXPECT_EQ("/path/hello.png", fn);
fn = "/path/hello1.gif";
EXPECT_EQ("{path}/{title}.{extension}", get_default_filename_format(fn, true, true, false, false));
EXPECT_EQ("/path/hello1.gif", fn);
fn = "/path/hello001.png";
EXPECT_EQ("{path}/{title}{frame001}.{extension}", get_default_filename_format(fn, true, true, false, false));
EXPECT_EQ("/path/hello.png", fn);
// When layers or tags are used in the filename format, the 1 is not converted to {frame1}
fn = "/path/hello1.png";
EXPECT_EQ("{path}/{title} #{tag} {frame}.{extension}", get_default_filename_format(fn, true, true, false, true));
EXPECT_EQ("/path/hello1.png", fn);
}

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2015 David Capello
// Copyright (c) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -28,6 +28,7 @@ namespace doc {
FrameTags* owner() const { return m_owner; }
frame_t fromFrame() const { return m_from; }
frame_t toFrame() const { return m_to; }
frame_t frames() const { return m_to - m_from + 1; }
const std::string& name() const { return m_name; }
color_t color() const { return m_color; }
AniDir aniDir() const { return m_aniDir; }

View File

@ -10,7 +10,7 @@
#endif
#include "app/app.h"
#include "app/app_options.h"
#include "app/cli/app_options.h"
#include "app/console.h"
#include "app/resource_finder.h"
#include "app/send_crash.h"

View File

@ -4,6 +4,6 @@
include_directories(${DUKTAPE_DIR})
add_library(duktape ${DUKTAPE_DIR}/duktape.c)
add_library(script-lib engine.cpp)
add_library(script-lib engine.cpp engine_delegate.cpp)
target_link_libraries(script-lib duktape)

View File

@ -0,0 +1,22 @@
// Aseprite Scripting Library
// Copyright (c) 2015-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "script/engine_delegate.h"
#include <cstdio>
namespace script {
void StdoutEngineDelegate::onConsolePrint(const char* text)
{
std::printf("%s\n", text);
}
} // namespace script

View File

@ -16,6 +16,11 @@ namespace script {
virtual void onConsolePrint(const char* text) = 0;
};
class StdoutEngineDelegate : public EngineDelegate {
public:
void onConsolePrint(const char* text) override;
};
}
#endif