mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-09 18:44:46 +00:00
814 lines
25 KiB
C++
814 lines
25 KiB
C++
// Aseprite
|
|
// Copyright (C) 2019 Igara Studio S.A.
|
|
// Copyright (C) 2001-2018 David Capello
|
|
//
|
|
// This program is distributed under the terms of
|
|
// the End-User License Agreement for Aseprite.
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "app/app.h"
|
|
#include "app/commands/new_params.h"
|
|
#include "app/context.h"
|
|
#include "app/context_access.h"
|
|
#include "app/doc.h"
|
|
#include "app/doc_exporter.h"
|
|
#include "app/file/file.h"
|
|
#include "app/file_selector.h"
|
|
#include "app/i18n/strings.h"
|
|
#include "app/modules/editors.h"
|
|
#include "app/pref/preferences.h"
|
|
#include "app/restore_visible_layers.h"
|
|
#include "app/ui/editor/editor.h"
|
|
#include "app/ui/layer_frame_comboboxes.h"
|
|
#include "app/ui/optional_alert.h"
|
|
#include "app/ui/status_bar.h"
|
|
#include "app/ui/timeline/timeline.h"
|
|
#include "base/bind.h"
|
|
#include "base/convert_to.h"
|
|
#include "base/fs.h"
|
|
#include "base/string.h"
|
|
#include "doc/frame_tag.h"
|
|
#include "doc/layer.h"
|
|
#include "fmt/format.h"
|
|
|
|
#include "export_sprite_sheet.xml.h"
|
|
|
|
#include <limits>
|
|
#include <sstream>
|
|
|
|
namespace app {
|
|
|
|
using namespace ui;
|
|
|
|
namespace {
|
|
|
|
// Special key value used in default preferences to know if by default
|
|
// the user wants to generate texture and/or files.
|
|
static const char* kSpecifiedFilename = "**filename**";
|
|
|
|
struct Fit {
|
|
int width;
|
|
int height;
|
|
int columns;
|
|
int rows;
|
|
int freearea;
|
|
Fit() : width(0), height(0), columns(0), rows(0), freearea(0) {
|
|
}
|
|
Fit(int width, int height, int columns, int rows, int freearea) :
|
|
width(width), height(height), columns(columns), rows(rows), freearea(freearea) {
|
|
}
|
|
};
|
|
|
|
// Calculate best size for the given sprite
|
|
// TODO this function was programmed in ten minutes, please optimize it
|
|
Fit best_fit(Sprite* sprite, int nframes, int borderPadding, int shapePadding, int innerPadding) {
|
|
int framew = sprite->width()+2*innerPadding;
|
|
int frameh = sprite->height()+2*innerPadding;
|
|
Fit result(framew*nframes, frameh, nframes, 1, std::numeric_limits<int>::max());
|
|
int w, h;
|
|
|
|
for (w=2; w < framew; w*=2)
|
|
;
|
|
for (h=2; h < frameh; h*=2)
|
|
;
|
|
|
|
int z = 0;
|
|
bool fully_contained = false;
|
|
while (!fully_contained) { // TODO at this moment we're not
|
|
// getting the best fit for less
|
|
// freearea, just the first one.
|
|
gfx::Rect rgnSize(w-2*borderPadding, h-2*borderPadding);
|
|
gfx::Region rgn(rgnSize);
|
|
int contained_frames = 0;
|
|
|
|
for (int v=0; v+frameh <= rgnSize.h && !fully_contained; v+=frameh+shapePadding) {
|
|
for (int u=0; u+framew <= rgnSize.w; u+=framew+shapePadding) {
|
|
gfx::Rect framerc = gfx::Rect(u, v, framew, frameh);
|
|
rgn.createSubtraction(rgn, gfx::Region(framerc));
|
|
|
|
++contained_frames;
|
|
if (nframes == contained_frames) {
|
|
fully_contained = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fully_contained) {
|
|
// TODO convert this to a template function gfx::area()
|
|
int freearea = 0;
|
|
for (const gfx::Rect& rgnRect : rgn)
|
|
freearea += rgnRect.w * rgnRect.h;
|
|
|
|
Fit fit(w, h, (w / framew), (h / frameh), freearea);
|
|
if (fit.freearea < result.freearea)
|
|
result = fit;
|
|
}
|
|
|
|
if ((++z) & 1) w *= 2;
|
|
else h *= 2;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
Fit calculate_sheet_size(Sprite* sprite, int nframes,
|
|
int columns, int rows,
|
|
int borderPadding, int shapePadding, int innerPadding) {
|
|
if (columns == 0) {
|
|
rows = MID(1, rows, nframes);
|
|
columns = ((nframes/rows) + ((nframes%rows) > 0 ? 1: 0));
|
|
}
|
|
else {
|
|
columns = MID(1, columns, nframes);
|
|
rows = ((nframes/columns) + ((nframes%columns) > 0 ? 1: 0));
|
|
}
|
|
|
|
return Fit(
|
|
2*borderPadding + (sprite->width()+2*innerPadding)*columns + (columns-1)*shapePadding,
|
|
2*borderPadding + (sprite->height()+2*innerPadding)*rows + (rows-1)*shapePadding,
|
|
columns, rows, 0);
|
|
}
|
|
|
|
bool ask_overwrite(bool askFilename, std::string filename,
|
|
bool askDataname, std::string dataname) {
|
|
if ((askFilename &&
|
|
!filename.empty() &&
|
|
base::is_file(filename)) ||
|
|
(askDataname &&
|
|
!dataname.empty() &&
|
|
base::is_file(dataname))) {
|
|
std::stringstream text;
|
|
|
|
if (base::is_file(filename))
|
|
text << "<<" << base::get_file_name(filename).c_str();
|
|
|
|
if (base::is_file(dataname))
|
|
text << "<<" << base::get_file_name(dataname).c_str();
|
|
|
|
int ret = OptionalAlert::show(
|
|
Preferences::instance().spriteSheet.showOverwriteFilesAlert,
|
|
1, // Yes is the default option when the alert dialog is disabled
|
|
fmt::format(Strings::alerts_overwrite_files_on_export_sprite_sheet(),
|
|
text.str()));
|
|
if (ret != 1)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
class ExportSpriteSheetWindow : public app::gen::ExportSpriteSheet {
|
|
public:
|
|
ExportSpriteSheetWindow(Site& site, DocumentPreferences& docPref)
|
|
: m_site(site)
|
|
, m_sprite(site.sprite())
|
|
, m_docPref(docPref)
|
|
, m_filenameAskOverwrite(true)
|
|
, m_dataFilenameAskOverwrite(true)
|
|
{
|
|
static_assert(
|
|
(int)app::SpriteSheetType::None == 0 &&
|
|
(int)app::SpriteSheetType::Horizontal == 1 &&
|
|
(int)app::SpriteSheetType::Vertical == 2 &&
|
|
(int)app::SpriteSheetType::Rows == 3 &&
|
|
(int)app::SpriteSheetType::Columns == 4,
|
|
"SpriteSheetType enum changed");
|
|
|
|
sheetType()->addItem("Horizontal Strip");
|
|
sheetType()->addItem("Vertical Strip");
|
|
sheetType()->addItem("By Rows");
|
|
sheetType()->addItem("By Columns");
|
|
if (m_docPref.spriteSheet.type() != app::SpriteSheetType::None)
|
|
sheetType()->setSelectedItemIndex((int)m_docPref.spriteSheet.type()-1);
|
|
|
|
fill_layers_combobox(
|
|
m_sprite, layers(), m_docPref.spriteSheet.layer());
|
|
|
|
fill_frames_combobox(
|
|
m_sprite, frames(), m_docPref.spriteSheet.frameTag());
|
|
|
|
openGenerated()->setSelected(m_docPref.spriteSheet.openGenerated());
|
|
trimEnabled()->setSelected(m_docPref.spriteSheet.trim());
|
|
|
|
borderPadding()->setTextf("%d", m_docPref.spriteSheet.borderPadding());
|
|
shapePadding()->setTextf("%d", m_docPref.spriteSheet.shapePadding());
|
|
innerPadding()->setTextf("%d", m_docPref.spriteSheet.innerPadding());
|
|
paddingEnabled()->setSelected(
|
|
m_docPref.spriteSheet.borderPadding() ||
|
|
m_docPref.spriteSheet.shapePadding() ||
|
|
m_docPref.spriteSheet.innerPadding());
|
|
paddingContainer()->setVisible(paddingEnabled()->isSelected());
|
|
|
|
for (int i=2; i<=8192; i*=2) {
|
|
std::string value = base::convert_to<std::string>(i);
|
|
if (i >= m_sprite->width()) fitWidth()->addItem(value);
|
|
if (i >= m_sprite->height()) fitHeight()->addItem(value);
|
|
}
|
|
|
|
if (m_docPref.spriteSheet.bestFit()) {
|
|
bestFit()->setSelected(true);
|
|
onBestFit();
|
|
}
|
|
else {
|
|
columns()->setTextf("%d", m_docPref.spriteSheet.columns());
|
|
rows()->setTextf("%d", m_docPref.spriteSheet.rows());
|
|
onColumnsChange();
|
|
|
|
if (m_docPref.spriteSheet.width() > 0 || m_docPref.spriteSheet.height() > 0) {
|
|
if (m_docPref.spriteSheet.width() > 0)
|
|
fitWidth()->getEntryWidget()->setTextf("%d", m_docPref.spriteSheet.width());
|
|
|
|
if (m_docPref.spriteSheet.height() > 0)
|
|
fitHeight()->getEntryWidget()->setTextf("%d", m_docPref.spriteSheet.height());
|
|
|
|
onSizeChange();
|
|
}
|
|
}
|
|
|
|
m_filename = m_docPref.spriteSheet.textureFilename();
|
|
imageEnabled()->setSelected(!m_filename.empty());
|
|
imageFilename()->setVisible(imageEnabled()->isSelected());
|
|
|
|
m_dataFilename = m_docPref.spriteSheet.dataFilename();
|
|
dataEnabled()->setSelected(!m_dataFilename.empty());
|
|
dataFormat()->setSelectedItemIndex(int(m_docPref.spriteSheet.dataFormat()));
|
|
listLayers()->setSelected(m_docPref.spriteSheet.listLayers());
|
|
listTags()->setSelected(m_docPref.spriteSheet.listFrameTags());
|
|
listSlices()->setSelected(m_docPref.spriteSheet.listSlices());
|
|
updateDataFields();
|
|
|
|
std::string base = site.document()->filename();
|
|
base = base::join_path(base::get_file_path(base), base::get_file_title(base));
|
|
|
|
if (m_filename.empty() ||
|
|
m_filename == kSpecifiedFilename) {
|
|
std::string defExt = Preferences::instance().spriteSheet.defaultExtension();
|
|
|
|
if (base::utf8_icmp(base::get_file_extension(site.document()->filename()), defExt) == 0)
|
|
m_filename = base + "-sheet." + defExt;
|
|
else
|
|
m_filename = base + "." + defExt;
|
|
}
|
|
|
|
if (m_dataFilename.empty() ||
|
|
m_dataFilename == kSpecifiedFilename)
|
|
m_dataFilename = base + ".json";
|
|
|
|
exportButton()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onExport, this));
|
|
sheetType()->Change.connect(&ExportSpriteSheetWindow::onSheetTypeChange, this);
|
|
columns()->Change.connect(base::Bind<void>(&ExportSpriteSheetWindow::onColumnsChange, this));
|
|
rows()->Change.connect(base::Bind<void>(&ExportSpriteSheetWindow::onRowsChange, this));
|
|
fitWidth()->Change.connect(base::Bind<void>(&ExportSpriteSheetWindow::onSizeChange, this));
|
|
fitHeight()->Change.connect(base::Bind<void>(&ExportSpriteSheetWindow::onSizeChange, this));
|
|
bestFit()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onBestFit, this));
|
|
borderPadding()->Change.connect(base::Bind<void>(&ExportSpriteSheetWindow::onPaddingChange, this));
|
|
shapePadding()->Change.connect(base::Bind<void>(&ExportSpriteSheetWindow::onPaddingChange, this));
|
|
innerPadding()->Change.connect(base::Bind<void>(&ExportSpriteSheetWindow::onPaddingChange, this));
|
|
imageEnabled()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onImageEnabledChange, this));
|
|
imageFilename()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onImageFilename, this));
|
|
dataEnabled()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onDataEnabledChange, this));
|
|
dataFilename()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onDataFilename, this));
|
|
paddingEnabled()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onPaddingEnabledChange, this));
|
|
frames()->Change.connect(base::Bind<void>(&ExportSpriteSheetWindow::onFramesChange, this));
|
|
openGenerated()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onOpenGeneratedChange, this));
|
|
|
|
onSheetTypeChange();
|
|
onFileNamesChange();
|
|
updateExportButton();
|
|
}
|
|
|
|
bool ok() const {
|
|
return closer() == exportButton();
|
|
}
|
|
|
|
app::SpriteSheetType spriteSheetTypeValue() const {
|
|
return (app::SpriteSheetType)(sheetType()->getSelectedItemIndex()+1);
|
|
}
|
|
|
|
int columnsValue() const {
|
|
if (spriteSheetTypeValue() != SpriteSheetType::Columns)
|
|
return columns()->textInt();
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
int rowsValue() const {
|
|
if (spriteSheetTypeValue() == SpriteSheetType::Columns)
|
|
return rows()->textInt();
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
int fitWidthValue() const {
|
|
return fitWidth()->getEntryWidget()->textInt();
|
|
}
|
|
|
|
int fitHeightValue() const {
|
|
return fitHeight()->getEntryWidget()->textInt();
|
|
}
|
|
|
|
bool bestFitValue() const {
|
|
return bestFit()->isSelected();
|
|
}
|
|
|
|
std::string filenameValue() const {
|
|
if (imageEnabled()->isSelected())
|
|
return m_filename;
|
|
else
|
|
return std::string();
|
|
}
|
|
|
|
std::string dataFilenameValue() const {
|
|
if (dataEnabled()->isSelected())
|
|
return m_dataFilename;
|
|
else
|
|
return std::string();
|
|
}
|
|
|
|
DocExporter::DataFormat dataFormatValue() const {
|
|
if (dataEnabled()->isSelected())
|
|
return DocExporter::DataFormat(dataFormat()->getSelectedItemIndex());
|
|
else
|
|
return DocExporter::DefaultDataFormat;
|
|
}
|
|
|
|
int borderPaddingValue() const {
|
|
if (paddingEnabled()->isSelected()) {
|
|
int value = borderPadding()->textInt();
|
|
return MID(0, value, 100);
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
int shapePaddingValue() const {
|
|
if (paddingEnabled()->isSelected()) {
|
|
int value = shapePadding()->textInt();
|
|
return MID(0, value, 100);
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
int innerPaddingValue() const {
|
|
if (paddingEnabled()->isSelected()) {
|
|
int value = innerPadding()->textInt();
|
|
return MID(0, value, 100);
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
bool trimValue() const {
|
|
return trimEnabled()->isSelected();
|
|
}
|
|
|
|
bool openGeneratedValue() const {
|
|
return openGenerated()->isSelected();
|
|
}
|
|
|
|
std::string layerValue() const {
|
|
return layers()->getValue();
|
|
}
|
|
|
|
std::string frameTagValue() const {
|
|
return frames()->getValue();
|
|
}
|
|
|
|
bool listLayersValue() const {
|
|
return listLayers()->isSelected();
|
|
}
|
|
|
|
bool listFrameTagsValue() const {
|
|
return listTags()->isSelected();
|
|
}
|
|
|
|
bool listSlicesValue() const {
|
|
return listSlices()->isSelected();
|
|
}
|
|
|
|
private:
|
|
|
|
void onExport() {
|
|
if (!ask_overwrite(m_filenameAskOverwrite, filenameValue(),
|
|
m_dataFilenameAskOverwrite, dataFilenameValue()))
|
|
return;
|
|
|
|
closeWindow(exportButton());
|
|
}
|
|
|
|
void onSheetTypeChange() {
|
|
bool rowsState = false;
|
|
bool colsState = false;
|
|
bool matrixState = false;
|
|
switch (spriteSheetTypeValue()) {
|
|
case app::SpriteSheetType::Rows:
|
|
colsState = true;
|
|
matrixState = true;
|
|
break;
|
|
case app::SpriteSheetType::Columns:
|
|
rowsState = true;
|
|
matrixState = true;
|
|
break;
|
|
}
|
|
|
|
columnsLabel()->setVisible(colsState);
|
|
columns()->setVisible(colsState);
|
|
|
|
rowsLabel()->setVisible(rowsState);
|
|
rows()->setVisible(rowsState);
|
|
|
|
fitWidthLabel()->setVisible(matrixState);
|
|
fitWidth()->setVisible(matrixState);
|
|
fitHeightLabel()->setVisible(matrixState);
|
|
fitHeight()->setVisible(matrixState);
|
|
bestFitFiller()->setVisible(matrixState);
|
|
bestFit()->setVisible(matrixState);
|
|
|
|
resize();
|
|
}
|
|
|
|
void onFileNamesChange() {
|
|
imageFilename()->setText("Select File: " + base::get_file_name(m_filename));
|
|
dataFilename()->setText("Select File: " + base::get_file_name(m_dataFilename));
|
|
resize();
|
|
}
|
|
|
|
void onColumnsChange() {
|
|
bestFit()->setSelected(false);
|
|
updateSizeFields();
|
|
}
|
|
|
|
void onRowsChange() {
|
|
bestFit()->setSelected(false);
|
|
updateSizeFields();
|
|
}
|
|
|
|
void onSizeChange() {
|
|
columns()->setTextf("%d", fitWidthValue() / m_sprite->width());
|
|
rows()->setTextf("%d", fitHeightValue() / m_sprite->height());
|
|
bestFit()->setSelected(false);
|
|
}
|
|
|
|
void onBestFit() {
|
|
updateSizeFields();
|
|
}
|
|
|
|
void onImageFilename() {
|
|
base::paths newFilename;
|
|
if (!app::show_file_selector(
|
|
"Save Sprite Sheet", m_filename,
|
|
get_writable_extensions(),
|
|
FileSelectorType::Save, newFilename))
|
|
return;
|
|
|
|
ASSERT(!newFilename.empty());
|
|
|
|
m_filename = newFilename.front();
|
|
m_filenameAskOverwrite = false; // Already asked in file selector
|
|
onFileNamesChange();
|
|
}
|
|
|
|
void onImageEnabledChange() {
|
|
m_filenameAskOverwrite = true;
|
|
|
|
imageFilename()->setVisible(imageEnabled()->isSelected());
|
|
updateExportButton();
|
|
resize();
|
|
}
|
|
|
|
void onDataFilename() {
|
|
// TODO hardcoded "json" extension
|
|
base::paths exts = { "json" };
|
|
base::paths newFilename;
|
|
if (!app::show_file_selector(
|
|
"Save JSON Data", m_dataFilename, exts,
|
|
FileSelectorType::Save, newFilename))
|
|
return;
|
|
|
|
ASSERT(!newFilename.empty());
|
|
|
|
m_dataFilename = newFilename.front();
|
|
m_dataFilenameAskOverwrite = false; // Already asked in file selector
|
|
onFileNamesChange();
|
|
}
|
|
|
|
void onDataEnabledChange() {
|
|
m_dataFilenameAskOverwrite = true;
|
|
|
|
updateDataFields();
|
|
updateExportButton();
|
|
resize();
|
|
}
|
|
|
|
void onPaddingEnabledChange() {
|
|
paddingContainer()->setVisible(paddingEnabled()->isSelected());
|
|
resize();
|
|
updateSizeFields();
|
|
}
|
|
|
|
void onPaddingChange() {
|
|
updateSizeFields();
|
|
}
|
|
|
|
void onFramesChange() {
|
|
updateSizeFields();
|
|
}
|
|
|
|
void onOpenGeneratedChange() {
|
|
updateExportButton();
|
|
}
|
|
|
|
void resize() {
|
|
gfx::Size reqSize = sizeHint();
|
|
moveWindow(gfx::Rect(origin(), reqSize));
|
|
layout();
|
|
invalidate();
|
|
}
|
|
|
|
void updateExportButton() {
|
|
exportButton()->setEnabled(
|
|
imageEnabled()->isSelected() ||
|
|
dataEnabled()->isSelected() ||
|
|
openGenerated()->isSelected());
|
|
}
|
|
|
|
void updateSizeFields() {
|
|
SelectedFrames selFrames;
|
|
calculate_selected_frames(m_site,
|
|
frameTagValue(),
|
|
selFrames);
|
|
|
|
frame_t nframes = selFrames.size();
|
|
|
|
Fit fit;
|
|
if (bestFit()->isSelected()) {
|
|
fit = best_fit(m_sprite, nframes,
|
|
borderPaddingValue(), shapePaddingValue(), innerPaddingValue());
|
|
}
|
|
else {
|
|
fit = calculate_sheet_size(
|
|
m_sprite, nframes,
|
|
columnsValue(),
|
|
rowsValue(),
|
|
borderPaddingValue(),
|
|
shapePaddingValue(),
|
|
innerPaddingValue());
|
|
}
|
|
|
|
columns()->setTextf("%d", fit.columns);
|
|
rows()->setTextf("%d", fit.rows);
|
|
fitWidth()->getEntryWidget()->setTextf("%d", fit.width);
|
|
fitHeight()->getEntryWidget()->setTextf("%d", fit.height);
|
|
}
|
|
|
|
void updateDataFields() {
|
|
bool state = dataEnabled()->isSelected();
|
|
dataFilename()->setVisible(state);
|
|
dataMeta()->setVisible(state);
|
|
}
|
|
|
|
Site& m_site;
|
|
Sprite* m_sprite;
|
|
DocumentPreferences& m_docPref;
|
|
std::string m_filename;
|
|
std::string m_dataFilename;
|
|
bool m_filenameAskOverwrite;
|
|
bool m_dataFilenameAskOverwrite;
|
|
};
|
|
|
|
struct ExportSpriteSheetParams : public NewParams {
|
|
Param<bool> ui { this, true, "ui" };
|
|
Param<bool> askOverwrite { this, true, { "askOverwrite", "ask-overwrite" } };
|
|
};
|
|
|
|
class ExportSpriteSheetCommand : public CommandWithNewParams<ExportSpriteSheetParams> {
|
|
public:
|
|
ExportSpriteSheetCommand();
|
|
|
|
protected:
|
|
bool onEnabled(Context* context) override;
|
|
void onExecute(Context* context) override;
|
|
};
|
|
|
|
ExportSpriteSheetCommand::ExportSpriteSheetCommand()
|
|
: CommandWithNewParams(CommandId::ExportSpriteSheet(), CmdRecordableFlag)
|
|
{
|
|
}
|
|
|
|
bool ExportSpriteSheetCommand::onEnabled(Context* context)
|
|
{
|
|
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable);
|
|
}
|
|
|
|
void ExportSpriteSheetCommand::onExecute(Context* context)
|
|
{
|
|
Site site = context->activeSite();
|
|
Doc* document = site.document();
|
|
Sprite* sprite = site.sprite();
|
|
DocumentPreferences& docPref(Preferences::instance().document(document));
|
|
|
|
#ifdef ENABLE_UI
|
|
bool askOverwrite = params().askOverwrite();
|
|
if (params().ui() && context->isUIAvailable()) {
|
|
ExportSpriteSheetWindow window(site, docPref);
|
|
window.openWindowInForeground();
|
|
if (!window.ok())
|
|
return;
|
|
|
|
docPref.spriteSheet.defined(true);
|
|
docPref.spriteSheet.type(window.spriteSheetTypeValue());
|
|
docPref.spriteSheet.columns(window.columnsValue());
|
|
docPref.spriteSheet.rows(window.rowsValue());
|
|
docPref.spriteSheet.width(window.fitWidthValue());
|
|
docPref.spriteSheet.height(window.fitHeightValue());
|
|
docPref.spriteSheet.bestFit(window.bestFitValue());
|
|
docPref.spriteSheet.textureFilename(window.filenameValue());
|
|
docPref.spriteSheet.dataFilename(window.dataFilenameValue());
|
|
docPref.spriteSheet.dataFormat(window.dataFormatValue());
|
|
docPref.spriteSheet.borderPadding(window.borderPaddingValue());
|
|
docPref.spriteSheet.shapePadding(window.shapePaddingValue());
|
|
docPref.spriteSheet.innerPadding(window.innerPaddingValue());
|
|
docPref.spriteSheet.trim(window.trimValue());
|
|
docPref.spriteSheet.openGenerated(window.openGeneratedValue());
|
|
docPref.spriteSheet.layer(window.layerValue());
|
|
docPref.spriteSheet.frameTag(window.frameTagValue());
|
|
docPref.spriteSheet.listLayers(window.listLayersValue());
|
|
docPref.spriteSheet.listFrameTags(window.listFrameTagsValue());
|
|
docPref.spriteSheet.listSlices(window.listSlicesValue());
|
|
|
|
// Default preferences for future sprites
|
|
DocumentPreferences& defPref(Preferences::instance().document(nullptr));
|
|
defPref.spriteSheet = docPref.spriteSheet;
|
|
defPref.spriteSheet.defined(false);
|
|
if (!defPref.spriteSheet.textureFilename().empty())
|
|
defPref.spriteSheet.textureFilename.setValueAndDefault(kSpecifiedFilename);
|
|
if (!defPref.spriteSheet.dataFilename().empty())
|
|
defPref.spriteSheet.dataFilename.setValueAndDefault(kSpecifiedFilename);
|
|
defPref.save();
|
|
|
|
askOverwrite = false; // Already asked in the ExportSpriteSheetWindow
|
|
}
|
|
#endif // ENABLE_UI
|
|
|
|
app::SpriteSheetType type = docPref.spriteSheet.type();
|
|
int columns = docPref.spriteSheet.columns();
|
|
int rows = docPref.spriteSheet.rows();
|
|
int width = docPref.spriteSheet.width();
|
|
int height = docPref.spriteSheet.height();
|
|
bool bestFit = docPref.spriteSheet.bestFit();
|
|
std::string filename = docPref.spriteSheet.textureFilename();
|
|
std::string dataFilename = docPref.spriteSheet.dataFilename();
|
|
DocExporter::DataFormat dataFormat = docPref.spriteSheet.dataFormat();
|
|
std::string layerName = docPref.spriteSheet.layer();
|
|
std::string frameTagName = docPref.spriteSheet.frameTag();
|
|
int borderPadding = docPref.spriteSheet.borderPadding();
|
|
int shapePadding = docPref.spriteSheet.shapePadding();
|
|
int innerPadding = docPref.spriteSheet.innerPadding();
|
|
borderPadding = MID(0, borderPadding, 100);
|
|
shapePadding = MID(0, shapePadding, 100);
|
|
innerPadding = MID(0, innerPadding, 100);
|
|
const bool trimCels = docPref.spriteSheet.trim();
|
|
const bool listLayers = docPref.spriteSheet.listLayers();
|
|
const bool listFrameTags = docPref.spriteSheet.listFrameTags();
|
|
const bool listSlices = docPref.spriteSheet.listSlices();
|
|
|
|
#ifdef ENABLE_UI
|
|
if (context->isUIAvailable() && askOverwrite) {
|
|
if (!ask_overwrite(true, filename,
|
|
true, dataFilename))
|
|
return; // Do not overwrite
|
|
}
|
|
#endif
|
|
|
|
SelectedFrames selFrames;
|
|
FrameTag* frameTag =
|
|
calculate_selected_frames(site, frameTagName, selFrames);
|
|
|
|
frame_t nframes = selFrames.size();
|
|
ASSERT(nframes > 0);
|
|
|
|
// If the user choose to render selected layers only, we can
|
|
// temporaly make them visible and hide the other ones.
|
|
RestoreVisibleLayers layersVisibility;
|
|
calculate_visible_layers(site, layerName, layersVisibility);
|
|
|
|
SelectedLayers selLayers;
|
|
if (layerName != kSelectedLayers) {
|
|
// TODO add a getLayerByName
|
|
for (Layer* layer : sprite->allLayers()) {
|
|
if (layer->name() == layerName) {
|
|
selLayers.insert(layer);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bestFit) {
|
|
Fit fit = best_fit(sprite, nframes, borderPadding, shapePadding, innerPadding);
|
|
columns = fit.columns;
|
|
rows = fit.rows;
|
|
width = fit.width;
|
|
height = fit.height;
|
|
}
|
|
|
|
int sheet_w = 0;
|
|
int sheet_h = 0;
|
|
|
|
switch (type) {
|
|
case app::SpriteSheetType::Horizontal:
|
|
columns = sprite->totalFrames();
|
|
rows = 1;
|
|
break;
|
|
case app::SpriteSheetType::Vertical:
|
|
columns = 1;
|
|
rows = nframes;
|
|
break;
|
|
case app::SpriteSheetType::Rows:
|
|
case app::SpriteSheetType::Columns:
|
|
if (width > 0) sheet_w = width;
|
|
if (height > 0) sheet_h = height;
|
|
break;
|
|
}
|
|
|
|
Fit fit = calculate_sheet_size(
|
|
sprite, nframes,
|
|
columns, rows,
|
|
borderPadding, shapePadding, innerPadding);
|
|
if (sheet_w == 0) sheet_w = fit.width;
|
|
if (sheet_h == 0) sheet_h = fit.height;
|
|
|
|
DocExporter exporter;
|
|
if (!filename.empty())
|
|
exporter.setTextureFilename(filename);
|
|
if (!dataFilename.empty()) {
|
|
exporter.setDataFilename(dataFilename);
|
|
exporter.setDataFormat(dataFormat);
|
|
}
|
|
exporter.setTextureWidth(sheet_w);
|
|
exporter.setTextureHeight(sheet_h);
|
|
exporter.setSpriteSheetType(type);
|
|
exporter.setBorderPadding(borderPadding);
|
|
exporter.setShapePadding(shapePadding);
|
|
exporter.setInnerPadding(innerPadding);
|
|
exporter.setTrimCels(trimCels);
|
|
if (listLayers) exporter.setListLayers(true);
|
|
if (listFrameTags) exporter.setListFrameTags(true);
|
|
if (listSlices) exporter.setListSlices(true);
|
|
exporter.addDocument(document, frameTag,
|
|
(!selLayers.empty() ? &selLayers: nullptr),
|
|
(!selFrames.empty() ? &selFrames: nullptr));
|
|
|
|
std::unique_ptr<Doc> newDocument(exporter.exportSheet(context));
|
|
if (!newDocument)
|
|
return;
|
|
|
|
#ifdef ENABLE_UI
|
|
if (context->isUIAvailable()) {
|
|
StatusBar* statusbar = StatusBar::instance();
|
|
if (statusbar)
|
|
statusbar->showTip(1000, "Sprite Sheet Generated");
|
|
}
|
|
#endif
|
|
|
|
// Copy background and grid preferences
|
|
{
|
|
DocumentPreferences& newDocPref(
|
|
Preferences::instance().document(newDocument.get()));
|
|
newDocPref.bg = docPref.bg;
|
|
newDocPref.grid = docPref.grid;
|
|
newDocPref.pixelGrid = docPref.pixelGrid;
|
|
Preferences::instance().removeDocument(newDocument.get());
|
|
}
|
|
|
|
if (docPref.spriteSheet.openGenerated()) {
|
|
// Setup a filename for the new document in case that user didn't
|
|
// save the file/specified one output filename.
|
|
if (filename.empty()) {
|
|
std::string fn = document->filename();
|
|
std::string ext = base::get_file_extension(fn);
|
|
if (!ext.empty())
|
|
ext.insert(0, 1, '.');
|
|
|
|
newDocument->setFilename(
|
|
base::join_path(base::get_file_path(fn),
|
|
base::get_file_title(fn) + "-Sheet") + ext);
|
|
}
|
|
|
|
newDocument->setContext(context);
|
|
newDocument.release();
|
|
}
|
|
}
|
|
|
|
Command* CommandFactory::createExportSpriteSheetCommand()
|
|
{
|
|
return new ExportSpriteSheetCommand;
|
|
}
|
|
|
|
} // namespace app
|