Add "Sprite Grid" source option to Export Sprite Sheet (fix #1982)

In this way we can export each grid cell/tile as an individual sprite,
and use the extrude option on each grid cell. We've added the
--split-grid CLI option too.
This commit is contained in:
David Capello 2022-08-19 17:21:56 -03:00
parent a2f61a3378
commit acb246e26b
9 changed files with 77 additions and 23 deletions

View File

@ -540,6 +540,7 @@
<option id="frame_tag" type="std::string" /> <option id="frame_tag" type="std::string" />
<option id="split_layers" type="bool" default="false" /> <option id="split_layers" type="bool" default="false" />
<option id="split_tags" type="bool" default="false" /> <option id="split_tags" type="bool" default="false" />
<option id="split_grid" type="bool" default="false" />
<option id="list_layers" type="bool" default="true" /> <option id="list_layers" type="bool" default="true" />
<option id="list_frame_tags" type="bool" default="true" /> <option id="list_frame_tags" type="bool" default="true" />
<option id="list_slices" type="bool" default="true" /> <option id="list_slices" type="bool" default="true" />

View File

@ -49,6 +49,7 @@ AppOptions::AppOptions(int argc, const char* argv[])
, m_splitLayers(m_po.add("split-layers").description("Save each visible layer of sprites\nas separated images in the sheet\n")) , 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_splitTags(m_po.add("split-tags").description("Save each tag as a separated file"))
, m_splitSlices(m_po.add("split-slices").description("Save each slice as a separated file")) , m_splitSlices(m_po.add("split-slices").description("Save each slice as a separated file"))
, m_splitGrid(m_po.add("split-grid").description("Save each grid tile as a separated file"))
, m_layer(m_po.add("layer").alias("import-layer").requiresValue("<name>").description("Include just the given layer in the sheet\nor save as operation")) , m_layer(m_po.add("layer").alias("import-layer").requiresValue("<name>").description("Include just the given layer in the sheet\nor save as operation"))
, m_allLayers(m_po.add("all-layers").description("Make all layers visible\nBy default hidden layers will be ignored")) , m_allLayers(m_po.add("all-layers").description("Make all layers visible\nBy default hidden layers will be ignored"))
, m_ignoreLayer(m_po.add("ignore-layer").requiresValue("<name>").description("Exclude the given layer in the sheet\nor save as operation")) , m_ignoreLayer(m_po.add("ignore-layer").requiresValue("<name>").description("Exclude the given layer in the sheet\nor save as operation"))

View File

@ -65,6 +65,7 @@ public:
const Option& splitLayers() const { return m_splitLayers; } const Option& splitLayers() const { return m_splitLayers; }
const Option& splitTags() const { return m_splitTags; } const Option& splitTags() const { return m_splitTags; }
const Option& splitSlices() const { return m_splitSlices; } const Option& splitSlices() const { return m_splitSlices; }
const Option& splitGrid() const { return m_splitGrid; }
const Option& layer() const { return m_layer; } const Option& layer() const { return m_layer; }
const Option& allLayers() const { return m_allLayers; } const Option& allLayers() const { return m_allLayers; }
const Option& ignoreLayer() const { return m_ignoreLayer; } const Option& ignoreLayer() const { return m_ignoreLayer; }
@ -132,6 +133,7 @@ private:
Option& m_splitLayers; Option& m_splitLayers;
Option& m_splitTags; Option& m_splitTags;
Option& m_splitSlices; Option& m_splitSlices;
Option& m_splitGrid;
Option& m_layer; Option& m_layer;
Option& m_allLayers; Option& m_allLayers;
Option& m_ignoreLayer; Option& m_ignoreLayer;

View File

@ -33,6 +33,7 @@ namespace app {
bool splitLayers = false; bool splitLayers = false;
bool splitTags = false; bool splitTags = false;
bool splitSlices = false; bool splitSlices = false;
bool splitGrid = false;
bool allLayers = false; bool allLayers = false;
bool listLayers = false; bool listLayers = false;
bool listTags = false; bool listTags = false;

View File

@ -283,6 +283,10 @@ int CliProcessor::process(Context* ctx)
else if (opt == &m_options.splitSlices()) { else if (opt == &m_options.splitSlices()) {
cof.splitSlices = true; cof.splitSlices = true;
} }
// --split-grid
else if (opt == &m_options.splitGrid()) {
cof.splitGrid = true;
}
// --layer <layer-name> // --layer <layer-name>
else if (opt == &m_options.layer()) { else if (opt == &m_options.layer()) {
cof.includeLayers.push_back(value.value()); cof.includeLayers.push_back(value.value());
@ -691,6 +695,7 @@ bool CliProcessor::openFile(Context* ctx, CliOpenFile& cof)
doc, tag, doc, tag,
cof.splitLayers, cof.splitLayers,
cof.splitTags, cof.splitTags,
cof.splitGrid,
(cof.hasLayersFilter() ? &filteredLayers: nullptr), (cof.hasLayersFilter() ? &filteredLayers: nullptr),
(!selFrames.empty() ? &selFrames: nullptr)); (!selFrames.empty() ? &selFrames: nullptr));
} }

View File

@ -67,6 +67,7 @@ enum Section {
enum Source { enum Source {
kSource_Sprite, kSource_Sprite,
kSource_SpriteGrid,
kSource_Tilesets, kSource_Tilesets,
}; };
@ -171,6 +172,7 @@ Doc* generate_sprite_sheet_from_params(
const bool mergeDuplicates = params.mergeDuplicates(); const bool mergeDuplicates = params.mergeDuplicates();
const bool splitLayers = params.splitLayers(); const bool splitLayers = params.splitLayers();
const bool splitTags = params.splitTags(); const bool splitTags = params.splitTags();
const bool splitGrid = params.splitGrid();
const bool listLayers = params.listLayers(); const bool listLayers = params.listLayers();
const bool listTags = params.listTags(); const bool listTags = params.listTags();
const bool listSlices = params.listSlices(); const bool listSlices = params.listSlices();
@ -204,13 +206,17 @@ Doc* generate_sprite_sheet_from_params(
} }
exporter.reset(); exporter.reset();
if (fromTilesets)
// Use each tileset from tilemap layers as a sprite
if (fromTilesets) {
exporter.addTilesetsSamples( exporter.addTilesetsSamples(
doc, doc,
!selLayers.empty() ? &selLayers: nullptr); !selLayers.empty() ? &selLayers: nullptr);
}
// Use the whole canvas as a sprite
else { else {
exporter.addDocumentSamples( exporter.addDocumentSamples(
doc, tag, splitLayers, splitTags, doc, tag, splitLayers, splitTags, splitGrid,
!selLayers.empty() ? &selLayers: nullptr, !selLayers.empty() ? &selLayers: nullptr,
!selFrames.empty() ? &selFrames: nullptr); !selFrames.empty() ? &selFrames: nullptr);
} }
@ -358,11 +364,15 @@ public:
} }
static_assert(kSource_Sprite == 0 && static_assert(kSource_Sprite == 0 &&
kSource_Tilesets == 1, kSource_SpriteGrid == 1 &&
kSource_Tilesets == 2,
"Source enum has changed"); "Source enum has changed");
source()->addItem(new ListItem("Sprite")); source()->addItem(new ListItem("Sprite"));
source()->addItem(new ListItem("Sprite Grid"));
source()->addItem(new ListItem("Tilesets")); source()->addItem(new ListItem("Tilesets"));
if (params.fromTilesets()) if (params.splitGrid())
source()->setSelectedItemIndex(int(kSource_SpriteGrid));
else if (params.fromTilesets())
source()->setSelectedItemIndex(int(kSource_Tilesets)); source()->setSelectedItemIndex(int(kSource_Tilesets));
fill_layers_combobox( fill_layers_combobox(
@ -529,6 +539,7 @@ public:
params.listLayers (listLayersValue()); params.listLayers (listLayersValue());
params.listTags (listTagsValue()); params.listTags (listTagsValue());
params.listSlices (listSlicesValue()); params.listSlices (listSlicesValue());
params.splitGrid (source()->getSelectedItemIndex() == int(kSource_SpriteGrid));
params.fromTilesets (source()->getSelectedItemIndex() == int(kSource_Tilesets)); params.fromTilesets (source()->getSelectedItemIndex() == int(kSource_Tilesets));
} }
@ -721,6 +732,10 @@ private:
return splitTags()->isSelected(); return splitTags()->isSelected();
} }
bool splitGridValue() const {
return (source()->getSelectedItemIndex() == int(kSource_SpriteGrid));
}
bool listLayersValue() const { bool listLayersValue() const {
return listLayers()->isSelected(); return listLayers()->isSelected();
} }
@ -1222,6 +1237,7 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
if (!params.tag.isSet()) params.tag( defPref.spriteSheet.frameTag()); if (!params.tag.isSet()) params.tag( defPref.spriteSheet.frameTag());
if (!params.splitLayers.isSet()) params.splitLayers( defPref.spriteSheet.splitLayers()); if (!params.splitLayers.isSet()) params.splitLayers( defPref.spriteSheet.splitLayers());
if (!params.splitTags.isSet()) params.splitTags( defPref.spriteSheet.splitTags()); if (!params.splitTags.isSet()) params.splitTags( defPref.spriteSheet.splitTags());
if (!params.splitGrid.isSet()) params.splitGrid( defPref.spriteSheet.splitGrid());
if (!params.listLayers.isSet()) params.listLayers( defPref.spriteSheet.listLayers()); if (!params.listLayers.isSet()) params.listLayers( defPref.spriteSheet.listLayers());
if (!params.listTags.isSet()) params.listTags( defPref.spriteSheet.listFrameTags()); if (!params.listTags.isSet()) params.listTags( defPref.spriteSheet.listFrameTags());
if (!params.listSlices.isSet()) params.listSlices( defPref.spriteSheet.listSlices()); if (!params.listSlices.isSet()) params.listSlices( defPref.spriteSheet.listSlices());
@ -1268,6 +1284,7 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
docPref.spriteSheet.frameTag (params.tag()); docPref.spriteSheet.frameTag (params.tag());
docPref.spriteSheet.splitLayers (params.splitLayers()); docPref.spriteSheet.splitLayers (params.splitLayers());
docPref.spriteSheet.splitTags (params.splitTags()); docPref.spriteSheet.splitTags (params.splitTags());
docPref.spriteSheet.splitGrid (params.splitGrid());
docPref.spriteSheet.listLayers (params.listLayers()); docPref.spriteSheet.listLayers (params.listLayers());
docPref.spriteSheet.listFrameTags (params.listTags()); docPref.spriteSheet.listFrameTags (params.listTags());
docPref.spriteSheet.listSlices (params.listSlices()); docPref.spriteSheet.listSlices (params.listSlices());

View File

@ -43,6 +43,7 @@ struct ExportSpriteSheetParams : public NewParams {
Param<std::string> tag { this, std::string(), "tag" }; Param<std::string> tag { this, std::string(), "tag" };
Param<bool> splitLayers { this, false, "splitLayers" }; Param<bool> splitLayers { this, false, "splitLayers" };
Param<bool> splitTags { this, false, "splitTags" }; Param<bool> splitTags { this, false, "splitTags" };
Param<bool> splitGrid { this, false, "splitGrid" };
Param<bool> listLayers { this, true, "listLayers" }; Param<bool> listLayers { this, true, "listLayers" };
Param<bool> listTags { this, true, "listTags" }; Param<bool> listTags { this, true, "listTags" };
Param<bool> listSlices { this, true, "listSlices" }; Param<bool> listSlices { this, true, "listSlices" };

View File

@ -96,11 +96,13 @@ typedef std::shared_ptr<gfx::Rect> SharedRectPtr;
DocExporter::Item::Item(Doc* doc, DocExporter::Item::Item(Doc* doc,
const doc::Tag* tag, const doc::Tag* tag,
const doc::SelectedLayers* selLayers, const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames) const doc::SelectedFrames* selFrames,
const bool splitGrid)
: doc(doc) : doc(doc)
, tag(tag) , tag(tag)
, selLayers(selLayers ? std::make_unique<doc::SelectedLayers>(*selLayers): nullptr) , selLayers(selLayers ? std::make_unique<doc::SelectedLayers>(*selLayers): nullptr)
, selFrames(selFrames ? std::make_unique<doc::SelectedFrames>(*selFrames): nullptr) , selFrames(selFrames ? std::make_unique<doc::SelectedFrames>(*selFrames): nullptr)
, splitGrid(splitGrid)
{ {
} }
@ -146,7 +148,8 @@ doc::SelectedFrames DocExporter::Item::getSelectedFrames() const
class DocExporter::Sample { class DocExporter::Sample {
public: public:
Sample(Doc* document, Sample(const gfx::Size& size,
Doc* document,
Sprite* sprite, Sprite* sprite,
const ImageRef& image, const ImageRef& image,
SelectedLayers* selLayers, SelectedLayers* selLayers,
@ -166,10 +169,9 @@ public:
m_extrude(extrude), m_extrude(extrude),
m_isLinked(false), m_isLinked(false),
m_isDuplicated(false), m_isDuplicated(false),
m_originalSize(image ? image->width(): sprite->width(), m_originalSize(size),
image ? image->height(): sprite->height()), m_trimmedBounds(size),
m_trimmedBounds(m_originalSize), m_inTextureBounds(std::make_shared<gfx::Rect>(size)) {
m_inTextureBounds(std::make_shared<gfx::Rect>(m_trimmedBounds)) {
} }
Doc* document() const { return m_document; } Doc* document() const { return m_document; }
@ -732,10 +734,11 @@ void DocExporter::addDocument(
Doc* doc, Doc* doc,
const doc::Tag* tag, const doc::Tag* tag,
const doc::SelectedLayers* selLayers, const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames) const doc::SelectedFrames* selFrames,
const bool splitGrid)
{ {
DX_TRACE("DX: addDocument doc=", doc, "tag=", tag); DX_TRACE("DX: addDocument doc=", doc, "tag=", tag);
m_documents.push_back(Item(doc, tag, selLayers, selFrames)); m_documents.push_back(Item(doc, tag, selLayers, selFrames, splitGrid));
} }
void DocExporter::addImage( void DocExporter::addImage(
@ -751,6 +754,7 @@ int DocExporter::addDocumentSamples(
const doc::Tag* thisTag, const doc::Tag* thisTag,
const bool splitLayers, const bool splitLayers,
const bool splitTags, const bool splitTags,
const bool splitGrid,
const doc::SelectedLayers* selLayers, const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames) const doc::SelectedFrames* selFrames)
{ {
@ -819,7 +823,7 @@ int DocExporter::addDocumentSamples(
SelectedLayers oneLayer; SelectedLayers oneLayer;
oneLayer.insert(layer); oneLayer.insert(layer);
addDocument(doc, tag, &oneLayer, thisSelFrames); addDocument(doc, tag, &oneLayer, thisSelFrames, splitGrid);
++items; ++items;
} }
} }
@ -830,13 +834,13 @@ int DocExporter::addDocumentSamples(
SelectedLayers oneLayer; SelectedLayers oneLayer;
oneLayer.insert(layer); oneLayer.insert(layer);
addDocument(doc, tag, &oneLayer, thisSelFrames); addDocument(doc, tag, &oneLayer, thisSelFrames, splitGrid);
++items; ++items;
} }
} }
} }
else { else {
addDocument(doc, tag, selLayers, thisSelFrames); addDocument(doc, tag, selLayers, thisSelFrames, splitGrid);
++items; ++items;
} }
} }
@ -957,6 +961,9 @@ void DocExporter::captureSamples(Samples& samples,
std::string filename = filename_formatter(format, fnInfo); std::string filename = filename_formatter(format, fnInfo);
Sample sample( Sample sample(
(item.image ? item.image->size():
item.splitGrid ? sprite->gridBounds().size():
sprite->size()),
doc, sprite, item.image, item.selLayers.get(), doc, sprite, item.image, item.selLayers.get(),
frame, innerTag, filename, frame, innerTag, filename,
m_innerPadding, m_extrude); m_innerPadding, m_extrude);
@ -1063,7 +1070,23 @@ void DocExporter::captureSamples(Samples& samples,
if (!alreadyTrimmed && m_trimSprite) if (!alreadyTrimmed && m_trimSprite)
sample.setTrimmedBounds(spriteBounds); sample.setTrimmedBounds(spriteBounds);
samples.addSample(sample); if (item.splitGrid) {
const gfx::Rect& gridBounds = sprite->gridBounds();
gfx::Point initPos(0, 0), pos;
initPos = pos = snap_to_grid(gridBounds, initPos, PreferSnapTo::BoxOrigin);
for (; pos.y+gridBounds.h <= spriteBounds.h; pos.y+=gridBounds.h) {
for (pos.x=initPos.x; pos.x+gridBounds.w <= spriteBounds.w; pos.x+=gridBounds.w) {
const gfx::Rect cellBounds(pos, gridBounds.size());
sample.setTrimmedBounds(cellBounds);
sample.setSharedBounds(std::make_shared<gfx::Rect>(sample.inTextureBounds()));
samples.addSample(sample);
}
}
}
else {
samples.addSample(sample);
}
DX_TRACE("DX: - Sample:", DX_TRACE("DX: - Sample:",
sample.document()->filename(), sample.document()->filename(),

View File

@ -77,12 +77,6 @@ namespace app {
void setListLayers(bool value) { m_listLayers = value; } void setListLayers(bool value) { m_listLayers = value; }
void setListSlices(bool value) { m_listSlices = value; } void setListSlices(bool value) { m_listSlices = value; }
void addDocument(
Doc* doc,
const doc::Tag* tag,
const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames);
void addImage( void addImage(
Doc* doc, Doc* doc,
const doc::ImageRef& image); const doc::ImageRef& image);
@ -92,6 +86,7 @@ namespace app {
const doc::Tag* tag, const doc::Tag* tag,
const bool splitLayers, const bool splitLayers,
const bool splitTags, const bool splitTags,
const bool splitGrid,
const doc::SelectedLayers* selLayers, const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames); const doc::SelectedFrames* selFrames);
@ -109,6 +104,12 @@ namespace app {
class SimpleLayoutSamples; class SimpleLayoutSamples;
class BestFitLayoutSamples; class BestFitLayoutSamples;
void addDocument(
Doc* doc,
const doc::Tag* tag,
const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames,
const bool splitGrid);
void captureSamples(Samples& samples, void captureSamples(Samples& samples,
base::task_token& token); base::task_token& token);
void layoutSamples(Samples& samples, void layoutSamples(Samples& samples,
@ -130,12 +131,14 @@ namespace app {
const doc::Tag* tag = nullptr; const doc::Tag* tag = nullptr;
std::unique_ptr<doc::SelectedLayers> selLayers; std::unique_ptr<doc::SelectedLayers> selLayers;
std::unique_ptr<doc::SelectedFrames> selFrames; std::unique_ptr<doc::SelectedFrames> selFrames;
bool splitGrid = false;
doc::ImageRef image; doc::ImageRef image;
Item(Doc* doc, Item(Doc* doc,
const doc::Tag* tag, const doc::Tag* tag,
const doc::SelectedLayers* selLayers, const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames); const doc::SelectedFrames* selFrames,
const bool splitGrid);
Item(Doc* doc, Item(Doc* doc,
const doc::ImageRef& image); const doc::ImageRef& image);
Item(Item&& other); Item(Item&& other);