diff --git a/data/pref.xml b/data/pref.xml
index 4631e9ec4..d1d157566 100644
--- a/data/pref.xml
+++ b/data/pref.xml
@@ -227,6 +227,8 @@
+
+
diff --git a/data/widgets/export_sprite_sheet.xml b/data/widgets/export_sprite_sheet.xml
index 95872a903..f512dbbd3 100644
--- a/data/widgets/export_sprite_sheet.xml
+++ b/data/widgets/export_sprite_sheet.xml
@@ -31,6 +31,12 @@
+
+
+
+
+
+
diff --git a/src/app/app.cpp b/src/app/app.cpp
index 090ce4c10..c0ffb0638 100644
--- a/src/app/app.cpp
+++ b/src/app/app.cpp
@@ -227,6 +227,7 @@ void App::initialize(const AppOptions& options)
std::string importLayer;
std::string importLayerSaveAs;
std::string filenameFormat;
+ std::string frameTagName;
for (const auto& value : options.values()) {
const AppOptions::Option* opt = value.option();
@@ -281,6 +282,10 @@ void App::initialize(const AppOptions& options)
importLayer = value.value();
importLayerSaveAs = value.value();
}
+ // --frame-tag
+ else if (opt == &options.frameTag()) {
+ frameTagName = value.value();
+ }
// --ignore-empty
else if (opt == &options.ignoreEmpty()) {
ignoreEmpty = true;
@@ -450,6 +455,10 @@ void App::initialize(const AppOptions& options)
getRecentFiles()->addRecentFile(filename.c_str());
if (m_exporter != NULL) {
+ FrameTag* frameTag = nullptr;
+ if (!frameTagName.empty())
+ frameTag = doc->sprite()->frameTags().getByName(frameTagName);
+
if (!importLayer.empty()) {
std::vector layers;
doc->sprite()->getLayersList(layers);
@@ -462,16 +471,16 @@ void App::initialize(const AppOptions& options)
}
}
if (foundLayer)
- m_exporter->addDocument(doc, foundLayer);
+ m_exporter->addDocument(doc, foundLayer, frameTag);
}
else if (splitLayers) {
std::vector layers;
doc->sprite()->getLayersList(layers);
for (auto layer : layers)
- m_exporter->addDocument(doc, layer);
+ m_exporter->addDocument(doc, layer, frameTag);
}
else
- m_exporter->addDocument(doc);
+ m_exporter->addDocument(doc, nullptr, frameTag);
}
}
diff --git a/src/app/app_options.cpp b/src/app/app_options.cpp
index 0f362168a..ad99b91b3 100644
--- a/src/app/app_options.cpp
+++ b/src/app/app_options.cpp
@@ -38,6 +38,7 @@ AppOptions::AppOptions(int argc, const char* argv[])
, m_sheetPack(m_po.add("sheet-pack").description("Use a packing algorithm to avoid waste of space\nin the texture"))
, m_splitLayers(m_po.add("split-layers").description("Import each layer of the next given sprite as\na separated image in the sheet"))
, m_layer(m_po.add("layer").alias("import-layer").requiresValue("").description("Include just the given layer in the sheet"))
+ , m_frameTag(m_po.add("frame-tag").requiresValue("").description("Include tagged frames in the sheet"))
, m_ignoreEmpty(m_po.add("ignore-empty").description("Do not export empty frames/cels"))
, m_borderPadding(m_po.add("border-padding").requiresValue("").description("Add padding on the texture borders"))
, m_shapePadding(m_po.add("shape-padding").requiresValue("").description("Add padding between frames"))
diff --git a/src/app/app_options.h b/src/app/app_options.h
index 6f90bc28e..9da1968f7 100644
--- a/src/app/app_options.h
+++ b/src/app/app_options.h
@@ -46,6 +46,7 @@ public:
const Option& sheetPack() const { return m_sheetPack; }
const Option& splitLayers() const { return m_splitLayers; }
const Option& layer() const { return m_layer; }
+ const Option& frameTag() const { return m_frameTag; }
const Option& ignoreEmpty() const { return m_ignoreEmpty; }
const Option& borderPadding() const { return m_borderPadding; }
const Option& shapePadding() const { return m_shapePadding; }
@@ -80,6 +81,7 @@ private:
Option& m_sheetPack;
Option& m_splitLayers;
Option& m_layer;
+ Option& m_frameTag;
Option& m_ignoreEmpty;
Option& m_borderPadding;
Option& m_shapePadding;
diff --git a/src/app/commands/cmd_export_sprite_sheet.cpp b/src/app/commands/cmd_export_sprite_sheet.cpp
index 851e20f32..72b12242f 100644
--- a/src/app/commands/cmd_export_sprite_sheet.cpp
+++ b/src/app/commands/cmd_export_sprite_sheet.cpp
@@ -17,12 +17,18 @@
#include "app/document_exporter.h"
#include "app/file/file.h"
#include "app/file_selector.h"
+#include "app/modules/editors.h"
#include "app/pref/preferences.h"
+#include "app/ui/editor/editor.h"
+#include "app/ui/main_window.h"
#include "app/ui/status_bar.h"
+#include "app/ui/timeline.h"
#include "base/bind.h"
#include "base/convert_to.h"
#include "base/fs.h"
#include "base/path.h"
+#include "doc/frame_tag.h"
+#include "doc/layer.h"
#include "generated_export_sprite_sheet.h"
@@ -35,6 +41,11 @@ using namespace ui;
namespace {
+ static const char* kAllLayers = "";
+ static const char* kAllFrames = "";
+ static const char* kSelectedLayers = "**selected-layers**";
+ static const char* kSelectedFrames = "**selected-frames**";
+
struct Fit {
int width;
int height;
@@ -49,8 +60,7 @@ namespace {
// Calculate best size for the given sprite
// TODO this function was programmed in ten minutes, please optimize it
- Fit best_fit(Sprite* sprite, int borderPadding, int shapePadding, int innerPadding) {
- int nframes = sprite->totalFrames();
+ 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, std::numeric_limits::max());
@@ -101,9 +111,8 @@ namespace {
return result;
}
- Fit calculate_sheet_size(Sprite* sprite, int columns, int borderPadding, int shapePadding, int innerPadding) {
- int nframes = sprite->totalFrames();
-
+ Fit calculate_sheet_size(Sprite* sprite, int nframes, int columns,
+ int borderPadding, int shapePadding, int innerPadding) {
columns = MID(1, columns, nframes);
int rows = ((nframes/columns) + ((nframes%columns) > 0 ? 1: 0));
@@ -136,10 +145,118 @@ namespace {
return true;
}
+ class SelectedFrameTag {
+ public:
+ static frame_t From() {
+ // TODO the range of selected frames should be in doc::Site.
+ DocumentRange range = App::instance()->getMainWindow()->getTimeline()->range();
+ if (range.enabled()) {
+ return range.frameBegin();
+ }
+ else if (current_editor) {
+ return current_editor->frame();
+ }
+ else
+ return 0;
+ }
+
+ static frame_t To() {
+ DocumentRange range = App::instance()->getMainWindow()->getTimeline()->range();
+ if (range.enabled()) {
+ return range.frameEnd();
+ }
+ else if (current_editor) {
+ return current_editor->frame();
+ }
+ else
+ return 0;
+ }
+
+ SelectedFrameTag() : m_frameTag(nullptr) {
+ }
+
+ ~SelectedFrameTag() {
+ if (m_frameTag) {
+ m_frameTag->owner()->remove(m_frameTag);
+ delete m_frameTag;
+ }
+ }
+
+ FrameTag* create(Sprite* sprite) {
+ m_frameTag = new FrameTag(From(), To());
+ sprite->frameTags().add(m_frameTag);
+ return m_frameTag;
+ }
+
+ private:
+ FrameTag* m_frameTag;
+ };
+
+ class SelectedLayers {
+ public:
+ ~SelectedLayers() {
+ for (auto item : m_restore)
+ item.first->setVisible(item.second);
+ }
+
+ void showSelectedLayers(Sprite* sprite) {
+ // TODO the range of selected frames should be in doc::Site.
+ DocumentRange range = App::instance()->getMainWindow()->getTimeline()->range();
+ if (!range.enabled()) {
+ if (current_editor) {
+ ASSERT(current_editor->sprite() == sprite);
+ range.startRange(sprite->layerToIndex(current_editor->layer()),
+ current_editor->frame(), DocumentRange::kCels);
+ range.endRange(sprite->layerToIndex(current_editor->layer()),
+ current_editor->frame());
+ }
+ else
+ return;
+ }
+
+ std::vector layers;
+ sprite->getLayersList(layers);
+ for (int i=0; iisVisible()) {
+ m_restore.push_back(std::make_pair(layer, layer->isVisible()));
+ layer->setVisible(selected);
+ }
+ }
+ }
+
+ private:
+ std::vector > m_restore;
+ };
+
}
class ExportSpriteSheetWindow : public app::gen::ExportSpriteSheet {
public:
+ class LayerItem : public ListItem {
+ public:
+ LayerItem(Layer* layer)
+ : ListItem("Layer: " + layer->name())
+ , m_layer(layer) {
+ }
+ Layer* layer() const { return m_layer; }
+ private:
+ Layer* m_layer;
+ };
+
+ class TagItem : public ListItem {
+ public:
+ TagItem(FrameTag* tag)
+ : ListItem("Tag: " + tag->name())
+ , m_tag(tag) {
+ }
+ FrameTag* tag() const { return m_tag; }
+ private:
+ FrameTag* m_tag;
+ };
+
ExportSpriteSheetWindow(Document* doc, Sprite* sprite,
DocumentPreferences& docPref)
: m_sprite(sprite)
@@ -160,6 +277,30 @@ public:
if (m_docPref.spriteSheet.type() != app::gen::SpriteSheetType::NONE)
sheetType()->setSelectedItemIndex((int)m_docPref.spriteSheet.type()-1);
+ layers()->addItem("Visible layers");
+ int i = layers()->addItem("Selected layers");
+ if (m_docPref.spriteSheet.layer() == kSelectedLayers)
+ layers()->setSelectedItemIndex(i);
+ {
+ std::vector layersList;
+ m_sprite->getLayersList(layersList);
+ for (Layer* layer : layersList) {
+ i = layers()->addItem(new LayerItem(layer));
+ if (m_docPref.spriteSheet.layer() == layer->name())
+ layers()->setSelectedItemIndex(i);
+ }
+ }
+
+ frames()->addItem("All frames");
+ i = frames()->addItem("Selected frames");
+ if (m_docPref.spriteSheet.frameTag() == kSelectedFrames)
+ frames()->setSelectedItemIndex(i);
+ for (FrameTag* tag : m_sprite->frameTags()) {
+ i = frames()->addItem(new TagItem(tag));
+ if (m_docPref.spriteSheet.frameTag() == tag->name())
+ frames()->setSelectedItemIndex(i);
+ }
+
openGenerated()->setSelected(m_docPref.spriteSheet.openGenerated());
borderPadding()->setTextf("%d", m_docPref.spriteSheet.borderPadding());
@@ -225,6 +366,7 @@ public:
dataEnabled()->Click.connect(Bind(&ExportSpriteSheetWindow::onDataEnabledChange, this));
dataFilename()->Click.connect(Bind(&ExportSpriteSheetWindow::onDataFilename, this));
paddingEnabled()->Click.connect(Bind(&ExportSpriteSheetWindow::onPaddingEnabledChange, this));
+ frames()->Change.connect(Bind(&ExportSpriteSheetWindow::onFramesChange, this));
onSheetTypeChange();
onFileNamesChange();
@@ -296,6 +438,24 @@ public:
return openGenerated()->isSelected();
}
+ std::string layerValue() {
+ if (LayerItem* item = dynamic_cast(layers()->getSelectedItem()))
+ return item->layer()->name();
+ else if (layers()->getSelectedItemIndex() == 1)
+ return kSelectedLayers;
+ else
+ return kAllLayers;
+ }
+
+ std::string frameTagValue() {
+ if (TagItem* item = dynamic_cast(frames()->getSelectedItem()))
+ return item->tag()->name();
+ else if (frames()->getSelectedItemIndex() == 1)
+ return kSelectedFrames;
+ else
+ return kAllFrames;
+ }
+
protected:
void onExport() {
@@ -387,6 +547,10 @@ protected:
updateSizeFields();
}
+ void onFramesChange() {
+ updateSizeFields();
+ }
+
private:
void resize() {
@@ -396,15 +560,25 @@ private:
}
void updateSizeFields() {
- Fit fit;
+ int nframes = m_sprite->totalFrames();
+ std::string tagName = frameTagValue();
+ if (tagName == kSelectedFrames) {
+ nframes = SelectedFrameTag::To() - SelectedFrameTag::From() + 1;
+ }
+ else {
+ FrameTag* frameTag = m_sprite->frameTags().getByName(tagName);
+ if (frameTag)
+ nframes = frameTag->toFrame() - frameTag->fromFrame() + 1;
+ }
+ Fit fit;
if (bestFit()->isSelected()) {
- fit = best_fit(m_sprite,
- borderPaddingValue(), shapePaddingValue(), innerPaddingValue());
+ fit = best_fit(m_sprite, nframes,
+ borderPaddingValue(), shapePaddingValue(), innerPaddingValue());
}
else {
fit = calculate_sheet_size(
- m_sprite, columnsValue(),
+ m_sprite, nframes, columnsValue(),
borderPaddingValue(),
shapePaddingValue(),
innerPaddingValue());
@@ -491,6 +665,8 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
docPref.spriteSheet.shapePadding(window.shapePaddingValue());
docPref.spriteSheet.innerPadding(window.innerPaddingValue());
docPref.spriteSheet.openGenerated(window.openGeneratedValue());
+ docPref.spriteSheet.layer(window.layerValue());
+ docPref.spriteSheet.frameTag(window.frameTagValue());
// Default preferences for future sprites
DocumentPreferences& defPref(Preferences::instance().document(nullptr));
@@ -508,6 +684,8 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
bool bestFit = docPref.spriteSheet.bestFit();
std::string filename = docPref.spriteSheet.textureFilename();
std::string dataFilename = docPref.spriteSheet.dataFilename();
+ 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();
@@ -521,20 +699,55 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
return; // Do not overwrite
}
+ // If the user want to export selected frames, we can create a
+ // temporal frame tag for that.
+ FrameTag* frameTag;
+ bool isTemporalTag = false;
+ SelectedFrameTag selectedFrameTag;
+ if (frameTagName == kSelectedFrames) {
+ frameTag = selectedFrameTag.create(sprite);
+ isTemporalTag = true;
+ }
+ else if (frameTagName != kAllFrames)
+ frameTag = sprite->frameTags().getByName(frameTagName);
+ else
+ frameTag = nullptr;
+
+ // If the user choose to render selected layers only, we can
+ // temporaly make them visible and hide the other ones.
+ Layer* layer = nullptr;
+ SelectedLayers layersVisibility;
+ if (layerName == kSelectedLayers) {
+ layersVisibility.showSelectedLayers(sprite);
+ }
+ else {
+ // TODO add a getLayerByName
+ std::vector layers;
+ sprite->getLayersList(layers);
+ for (Layer* l : layers) {
+ if (l->name() == layerName) {
+ layer = l;
+ break;
+ }
+ }
+ }
+
+ int nframes = (frameTag ? frameTag->toFrame() - frameTag->fromFrame() + 1:
+ sprite->totalFrames());
+
if (bestFit) {
- Fit fit = best_fit(sprite, borderPadding, shapePadding, innerPadding);
+ Fit fit = best_fit(sprite, nframes, borderPadding, shapePadding, innerPadding);
columns = fit.columns;
width = fit.width;
height = fit.height;
}
- frame_t nframes = sprite->totalFrames();
int sheet_w = 0;
int sheet_h = 0;
switch (type) {
case app::gen::SpriteSheetType::HORIZONTAL_STRIP:
- columns = nframes;
+ columns = sprite->totalFrames();
break;
case app::gen::SpriteSheetType::VERTICAL_STRIP:
columns = 1;
@@ -545,7 +758,10 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
break;
}
- Fit fit = calculate_sheet_size(sprite, columns,
+ Fit fit = calculate_sheet_size(
+ sprite,
+ nframes,
+ columns,
borderPadding, shapePadding, innerPadding);
columns = fit.columns;
if (sheet_w == 0) sheet_w = fit.width;
@@ -561,7 +777,7 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
exporter.setBorderPadding(borderPadding);
exporter.setShapePadding(shapePadding);
exporter.setInnerPadding(innerPadding);
- exporter.addDocument(document);
+ exporter.addDocument(document, layer, frameTag, isTemporalTag);
base::UniquePtr newDocument(exporter.exportSheet());
if (!newDocument)
diff --git a/src/app/document_exporter.cpp b/src/app/document_exporter.cpp
index c7f720034..98c8a930d 100644
--- a/src/app/document_exporter.cpp
+++ b/src/app/document_exporter.cpp
@@ -86,6 +86,32 @@ private:
typedef base::SharedPtr SampleBoundsPtr;
+int DocumentExporter::Item::frames() const
+{
+ if (frameTag) {
+ int result = frameTag->toFrame() - frameTag->fromFrame() + 1;
+ return MID(1, result, doc->sprite()->totalFrames());
+ }
+ else
+ return doc->sprite()->totalFrames();
+}
+
+int DocumentExporter::Item::fromFrame() const
+{
+ if (frameTag)
+ return MID(0, frameTag->fromFrame(), doc->sprite()->lastFrame());
+ else
+ return 0;
+}
+
+int DocumentExporter::Item::toFrame() const
+{
+ if (frameTag)
+ return MID(0, frameTag->toFrame(), doc->sprite()->lastFrame());
+ else
+ return doc->sprite()->lastFrame();
+}
+
class DocumentExporter::Sample {
public:
Sample(Document* document, Sprite* sprite, Layer* layer,
@@ -336,24 +362,29 @@ void DocumentExporter::captureSamples(Samples& samples)
Document* doc = item.doc;
Sprite* sprite = doc->sprite();
Layer* layer = item.layer;
- bool hasFrames = (doc->sprite()->totalFrames() > frame_t(1));
- bool hasLayer = (layer != NULL);
+ 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)
- format = "{title} ({layer}) {frame}.{extension}";
- else if (hasFrames)
- format = "{title} {frame}.{extension}";
- else if (hasLayer)
- format = "{title} ({layer}).{extension}";
+ if (hasFrames || hasLayer | hasFrameTag) {
+ format = "{title}";
+ if (hasLayer ) format += " ({layer})";
+ if (hasFrameTag) format += " #{tag}";
+ if (hasFrames ) format += " {frame}";
+ format += ".{extension}";
+ }
else
format = "{name}";
}
- for (frame_t frame=frame_t(0);
- frametotalFrames(); ++frame) {
- FrameTag* innerTag = sprite->frameTags().innerTag(frame);
+ frame_t frameFirst = item.fromFrame();
+ frame_t frameLast = item.toFrame();
+ for (frame_t frame=frameFirst; frame<=frameLast; ++frame) {
+ FrameTag* innerTag = (frameTag ? frameTag: sprite->frameTags().innerTag(frame));
FrameTag* outerTag = sprite->frameTags().outerTag(frame);
FilenameInfo fnInfo;
fnInfo
@@ -361,7 +392,7 @@ void DocumentExporter::captureSamples(Samples& samples)
.layerName(layer ? layer->name(): "")
.innerTagName(innerTag ? innerTag->name(): "")
.outerTagName(outerTag ? outerTag->name(): "")
- .frame((sprite->totalFrames() > frame_t(1)) ? frame: frame_t(-1));
+ .frame((frames > 1) ? frame-frameFirst: frame_t(-1));
std::string filename = filename_formatter(format, fnInfo);
@@ -389,7 +420,9 @@ void DocumentExporter::captureSamples(Samples& samples)
break;
}
}
- ASSERT(done);
+ // "done" variable can be false here, e.g. when we export a
+ // frame tag and the first linked cel is outside the tag range.
+ ASSERT(done || (!done && frameTag));
}
if (!done && (m_ignoreEmptyCels || m_trimCels)) {
diff --git a/src/app/document_exporter.h b/src/app/document_exporter.h
index 4b1cbebbf..47205a845 100644
--- a/src/app/document_exporter.h
+++ b/src/app/document_exporter.h
@@ -18,6 +18,7 @@
#include
namespace doc {
+ class FrameTag;
class Image;
class Layer;
}
@@ -60,8 +61,11 @@ namespace app {
void setTrimCels(bool trim) { m_trimCels = trim; }
void setFilenameFormat(const std::string& format) { m_filenameFormat = format; }
- void addDocument(Document* document, doc::Layer* layer = NULL) {
- m_documents.push_back(Item(document, layer));
+ void addDocument(Document* document,
+ doc::Layer* layer = nullptr,
+ doc::FrameTag* tag = nullptr,
+ bool temporalTag = false) {
+ m_documents.push_back(Item(document, layer, tag, temporalTag));
}
Document* exportSheet();
@@ -83,9 +87,20 @@ namespace app {
public:
Document* doc;
doc::Layer* layer;
- Item(Document* doc, doc::Layer* layer)
- : doc(doc), layer(layer) {
+ doc::FrameTag* frameTag;
+ bool temporalTag;
+
+ Item(Document* doc,
+ doc::Layer* layer,
+ doc::FrameTag* frameTag,
+ bool temporalTag)
+ : doc(doc), layer(layer), frameTag(frameTag)
+ , temporalTag(temporalTag) {
}
+
+ int frames() const;
+ int fromFrame() const;
+ int toFrame() const;
};
typedef std::vector- Items;