Add support to export all/selected/one layer/frame/frame tag

This commit includes support to export tagged frames using the command
line --frame-tag option.

Fix #415, fix #570, fix #607, fix #745
This commit is contained in:
David Capello 2015-08-20 22:30:33 -03:00
parent d1799c037a
commit ff9693b29d
8 changed files with 318 additions and 34 deletions

View File

@ -227,6 +227,8 @@
<option id="shape_padding" type="int" default="0" />
<option id="inner_padding" type="int" default="0" />
<option id="open_generated" type="bool" default="false" />
<option id="layer" type="std::string" />
<option id="frame_tag" type="std::string" />
</section>
<section id="import_sprite_sheet">
<option id="bounds" type="gfx::Rect" default="gfx::Rect(0, 0, 16, 16)" />

View File

@ -31,6 +31,12 @@
<hbox id="best_fit_filler" />
<check cell_hspan="3" id="best_fit" text="Best fit for texture" />
<label text="Layers:" />
<combobox id="layers" text="" cell_hspan="3" />
<label text="Frames:" />
<combobox id="frames" text="" cell_hspan="3" />
<label text="Save As:" />
<button id="image_filename" cell_hspan="3" />

View File

@ -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 <tag-name>
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<Layer*> 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<Layer*> 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);
}
}

View File

@ -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("<name>").description("Include just the given layer in the sheet"))
, m_frameTag(m_po.add("frame-tag").requiresValue("<name>").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("<value>").description("Add padding on the texture borders"))
, m_shapePadding(m_po.add("shape-padding").requiresValue("<value>").description("Add padding between frames"))

View File

@ -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;

View File

@ -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<int>::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<Layer*> layers;
sprite->getLayersList(layers);
for (int i=0; i<int(layers.size()); ++i) {
Layer* layer = layers[i];
bool selected = range.inRange(LayerIndex(i));
if (selected != layer->isVisible()) {
m_restore.push_back(std::make_pair(layer, layer->isVisible()));
layer->setVisible(selected);
}
}
}
private:
std::vector<std::pair<Layer*, bool> > 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<Layer*> 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<void>(&ExportSpriteSheetWindow::onDataEnabledChange, this));
dataFilename()->Click.connect(Bind<void>(&ExportSpriteSheetWindow::onDataFilename, this));
paddingEnabled()->Click.connect(Bind<void>(&ExportSpriteSheetWindow::onPaddingEnabledChange, this));
frames()->Change.connect(Bind<void>(&ExportSpriteSheetWindow::onFramesChange, this));
onSheetTypeChange();
onFileNamesChange();
@ -296,6 +438,24 @@ public:
return openGenerated()->isSelected();
}
std::string layerValue() {
if (LayerItem* item = dynamic_cast<LayerItem*>(layers()->getSelectedItem()))
return item->layer()->name();
else if (layers()->getSelectedItemIndex() == 1)
return kSelectedLayers;
else
return kAllLayers;
}
std::string frameTagValue() {
if (TagItem* item = dynamic_cast<TagItem*>(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,
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<Layer*> 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<Document> newDocument(exporter.exportSheet());
if (!newDocument)

View File

@ -86,6 +86,32 @@ private:
typedef base::SharedPtr<SampleBounds> 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);
frame<sprite->totalFrames(); ++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)) {

View File

@ -18,6 +18,7 @@
#include <vector>
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<Item> Items;