From 2c0d0d36826ad9f3e13b4e58e490667e074f275d Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 11 Apr 2017 18:45:51 -0300 Subject: [PATCH] Add --slice and --split-slice CLI params (#721) --- src/app/cli/app_options.cpp | 2 + src/app/cli/app_options.h | 4 + src/app/cli/cli_open_file.cpp | 9 +- src/app/cli/cli_open_file.h | 6 ++ src/app/cli/cli_processor.cpp | 148 ++++++++++++++++----------- src/app/cli/default_cli_delegate.cpp | 3 + src/app/cli/preview_cli_delegate.cpp | 4 + src/app/commands/cmd_save_file.cpp | 3 +- src/app/commands/cmd_save_file.h | 3 +- src/app/file/file.cpp | 39 +++++-- src/app/file/file.h | 4 + src/app/filename_formatter.cpp | 6 ++ src/app/filename_formatter.h | 10 +- 13 files changed, 173 insertions(+), 68 deletions(-) diff --git a/src/app/cli/app_options.cpp b/src/app/cli/app_options.cpp index 2c4246c3a..4fbf0df87 100644 --- a/src/app/cli/app_options.cpp +++ b/src/app/cli/app_options.cpp @@ -44,6 +44,7 @@ AppOptions::AppOptions(int argc, const char* argv[]) , m_sheetPack(m_po.add("sheet-pack").description("Same as --sheet-type packed")) , 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_splitSlices(m_po.add("split-slices").description("Save each slice as a separated file")) , m_layer(m_po.add("layer").alias("import-layer").requiresValue("").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_ignoreLayer(m_po.add("ignore-layer").requiresValue("").description("Exclude the given layer in the sheet\nor save as operation")) @@ -55,6 +56,7 @@ AppOptions::AppOptions(int argc, const char* argv[]) , m_innerPadding(m_po.add("inner-padding").requiresValue("").description("Add padding inside each frame")) , m_trim(m_po.add("trim").description("Trim all images before exporting")) , m_crop(m_po.add("crop").requiresValue("x,y,width,height").description("Crop all the images to the given rectangle")) + , m_slice(m_po.add("slice").requiresValue("").description("Crop the sprite to the given slice area")) , m_filenameFormat(m_po.add("filename-format").requiresValue("").description("Special format to generate filenames")) #ifdef ENABLE_SCRIPTING , m_script(m_po.add("script").requiresValue("").description("Execute a specific script")) diff --git a/src/app/cli/app_options.h b/src/app/cli/app_options.h index 3c9fb2b7f..0d3df6c1a 100644 --- a/src/app/cli/app_options.h +++ b/src/app/cli/app_options.h @@ -58,6 +58,7 @@ public: const Option& sheetPack() const { return m_sheetPack; } const Option& splitLayers() const { return m_splitLayers; } const Option& splitTags() const { return m_splitTags; } + const Option& splitSlices() const { return m_splitSlices; } const Option& layer() const { return m_layer; } const Option& allLayers() const { return m_allLayers; } const Option& ignoreLayer() const { return m_ignoreLayer; } @@ -69,6 +70,7 @@ public: const Option& innerPadding() const { return m_innerPadding; } const Option& trim() const { return m_trim; } const Option& crop() const { return m_crop; } + const Option& slice() const { return m_slice; } const Option& filenameFormat() const { return m_filenameFormat; } #ifdef ENABLE_SCRIPTING const Option& script() const { return m_script; } @@ -108,6 +110,7 @@ private: Option& m_sheetPack; Option& m_splitLayers; Option& m_splitTags; + Option& m_splitSlices; Option& m_layer; Option& m_allLayers; Option& m_ignoreLayer; @@ -119,6 +122,7 @@ private: Option& m_innerPadding; Option& m_trim; Option& m_crop; + Option& m_slice; Option& m_filenameFormat; #ifdef ENABLE_SCRIPTING Option& m_script; diff --git a/src/app/cli/cli_open_file.cpp b/src/app/cli/cli_open_file.cpp index ee18a4802..9050c6a0f 100644 --- a/src/app/cli/cli_open_file.cpp +++ b/src/app/cli/cli_open_file.cpp @@ -25,6 +25,7 @@ CliOpenFile::CliOpenFile() toFrame = -1; splitLayers = false; splitTags = false; + splitSlices = false; allLayers = false; listLayers = false; listTags = false; @@ -38,10 +39,16 @@ CliOpenFile::CliOpenFile() FileOpROI CliOpenFile::roi() const { ASSERT(document); + SelectedFrames selFrames; if (hasFrameRange()) selFrames.insert(fromFrame, toFrame); - return FileOpROI(document, frameTag, selFrames, true); + + return FileOpROI(document, + slice, + frameTag, + selFrames, + true); } } // namespace app diff --git a/src/app/cli/cli_open_file.h b/src/app/cli/cli_open_file.h index f7adfbace..1a92a6138 100644 --- a/src/app/cli/cli_open_file.h +++ b/src/app/cli/cli_open_file.h @@ -24,11 +24,13 @@ namespace app { std::string filename; std::string filenameFormat; std::string frameTag; + std::string slice; std::vector includeLayers; std::vector excludeLayers; doc::frame_t fromFrame, toFrame; bool splitLayers; bool splitTags; + bool splitSlices; bool allLayers; bool listLayers; bool listTags; @@ -44,6 +46,10 @@ namespace app { return (!frameTag.empty()); } + bool hasSlice() const { + return (!slice.empty()); + } + bool hasFrameRange() const { return (fromFrame >= 0 && toFrame >= 0); } diff --git a/src/app/cli/cli_processor.cpp b/src/app/cli/cli_processor.cpp index 868625b7b..6cfaf1d5c 100644 --- a/src/app/cli/cli_processor.cpp +++ b/src/app/cli/cli_processor.cpp @@ -31,6 +31,7 @@ #include "doc/layer.h" #include "doc/selected_frames.h" #include "doc/selected_layers.h" +#include "doc/slice.h" namespace app { @@ -201,6 +202,10 @@ void CliProcessor::process() else if (opt == &m_options.splitTags()) { cof.splitTags = true; } + // --split-slice + else if (opt == &m_options.splitSlices()) { + cof.splitSlices = true; + } // --layer else if (opt == &m_options.layer()) { cof.includeLayers.push_back(value.value()); @@ -270,6 +275,10 @@ void CliProcessor::process() cof.crop.w = base::convert_to(parts[2]); cof.crop.h = base::convert_to(parts[3]); } + // --slice + else if (opt == &m_options.slice()) { + cof.slice = value.value(); + } // --filename-format else if (opt == &m_options.filenameFormat()) { cof.filenameFormat = value.value(); @@ -281,15 +290,18 @@ void CliProcessor::process() 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. + // Automatic --split-layer, --split-tags, --split-slices + // in case the output filename already contains {layer}, + // {tag}, or {slice} template elements. bool hasLayerTemplate = (is_layer_in_filename_format(fn) || is_group_in_filename_format(fn)); bool hasTagTemplate = is_tag_in_filename_format(fn); - if (hasLayerTemplate || hasTagTemplate) { + bool hasSliceTemplate = is_slice_in_filename_format(fn); + + if (hasLayerTemplate || hasTagTemplate || hasSliceTemplate) { cof.splitLayers = (cof.splitLayers || hasLayerTemplate); cof.splitTags = (cof.splitTags || hasTagTemplate); + cof.splitSlices = (cof.splitSlices || hasSliceTemplate); cof.filenameFormat = get_default_filename_format( fn, @@ -510,7 +522,6 @@ void CliProcessor::saveFile(const CliOpenFile& cof) 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; @@ -521,7 +532,9 @@ void CliProcessor::saveFile(const CliOpenFile& cof) cropParams.set("y", base::convert_to(cof.crop.y).c_str()); cropParams.set("width", base::convert_to(cof.crop.w).c_str()); cropParams.set("height", base::convert_to(cof.crop.h).c_str()); - ctx->executeCommand(cropCommand, cropParams); + ctx->executeCommand( + CommandsModule::instance()->getCommandByName(CommandId::CropSprite), + cropParams); } std::string fn = cof.filename; @@ -575,70 +588,91 @@ void CliProcessor::saveFile(const CliOpenFile& cof) frameTags.push_back(nullptr); } + std::vector slices; + if (cof.hasSlice()) { + slices.push_back( + doc->sprite()->slices().getByName(cof.slice)); + } + else { + doc::Slices& origSlices = cof.document->sprite()->slices(); + if (cof.splitSlices && !origSlices.empty()) { + for (doc::Slice* slice : origSlices) + slices.push_back(slice); + } + else + slices.push_back(nullptr); + } + bool layerInFormat = is_layer_in_filename_format(fn); bool groupInFormat = is_group_in_filename_format(fn); - for (doc::FrameTag* frameTag : frameTags) { - // For each layer, hide other ones and save the sprite. - for (doc::Layer* layer : layers) { - RestoreVisibleLayers layersVisibility; + for (doc::Slice* slice : slices) { + for (doc::FrameTag* frameTag : frameTags) { + // For each layer, hide other ones and save the sprite. + for (doc::Layer* layer : layers) { + RestoreVisibleLayers layersVisibility; - if (cof.splitLayers) { - ASSERT(layer); + if (cof.splitLayers) { + ASSERT(layer); - // If the user doesn't want all layers and this one is hidden. - if (!layer->isVisible()) - continue; // Just ignore this layer. + // If the user doesn't want all layers and this one is hidden. + if (!layer->isVisible()) + continue; // Just ignore this layer. - // Make this layer ("show") the only one visible. - layersVisibility.showLayer(layer); - } - else if (!filteredLayers.empty()) - layersVisibility.showSelectedLayers(doc->sprite(), filteredLayers); - - if (layer) { - if ((layerInFormat && layer->isGroup()) || - (!layerInFormat && groupInFormat && !layer->isGroup())) { - continue; + // Make this layer ("show") the only one visible. + layersVisibility.showLayer(layer); } - } + else if (!filteredLayers.empty()) + layersVisibility.showSelectedLayers(doc->sprite(), filteredLayers); - // 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); + if (layer) { + if ((layerInFormat && layer->isGroup()) || + (!layerInFormat && groupInFormat && !layer->isGroup())) { + continue; + } + } - CliOpenFile itemCof = cof; - FilenameInfo fnInfo; - fnInfo.filename(fn); - if (layer) { - fnInfo.layerName(layer->name()); + // 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); - if (layer->isGroup()) - fnInfo.groupName(layer->name()); - else if (layer->parent() != layer->sprite()->root()) - fnInfo.groupName(layer->parent()->name()); + CliOpenFile itemCof = cof; + FilenameInfo fnInfo; + fnInfo.filename(fn); + if (layer) { + fnInfo.layerName(layer->name()); - itemCof.includeLayers.push_back(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); + if (layer->isGroup()) + fnInfo.groupName(layer->name()); + else if (layer->parent() != layer->sprite()->root()) + fnInfo.groupName(layer->parent()->name()); - // Call delegate - m_delegate->saveFile(itemCof); + itemCof.includeLayers.push_back(layer->name()); + } + if (frameTag) { + fnInfo + .innerTagName(frameTag->name()) + .outerTagName(frameTag->name()); + itemCof.frameTag = frameTag->name(); + } + if (slice) { + fnInfo.sliceName(slice->name()); + itemCof.slice = slice->name(); + } + itemCof.filename = filename_formatter(filenameFormat, fnInfo); + itemCof.filenameFormat = filename_formatter(filenameFormat, fnInfo, false); - if (cof.trim) { - ctx->executeCommand(undoCommand); - clearUndo = true; + // Call delegate + m_delegate->saveFile(itemCof); + + if (cof.trim) { + ctx->executeCommand(undoCommand); + clearUndo = true; + } } } } diff --git a/src/app/cli/default_cli_delegate.cpp b/src/app/cli/default_cli_delegate.cpp index 28d88d4f2..23e7752e8 100644 --- a/src/app/cli/default_cli_delegate.cpp +++ b/src/app/cli/default_cli_delegate.cpp @@ -88,6 +88,9 @@ void DefaultCliDelegate::saveFile(const CliOpenFile& cof) params.set("from-frame", base::convert_to(cof.fromFrame).c_str()); params.set("to-frame", base::convert_to(cof.toFrame).c_str()); } + if (cof.hasSlice()) { + params.set("slice", cof.slice.c_str()); + } ctx->executeCommand(saveAsCommand, params); } diff --git a/src/app/cli/preview_cli_delegate.cpp b/src/app/cli/preview_cli_delegate.cpp index c7844e2ef..959af43c5 100644 --- a/src/app/cli/preview_cli_delegate.cpp +++ b/src/app/cli/preview_cli_delegate.cpp @@ -108,6 +108,10 @@ void PreviewCliDelegate::saveFile(const CliOpenFile& cof) std::cout << " - Frame tag: '" << cof.frameTag << "'\n"; } + if (cof.hasSlice()) { + std::cout << " - Slice: '" << cof.slice << "'\n"; + } + if (cof.hasFrameRange()) { const auto& selFrames = cof.roi().selectedFrames(); if (!selFrames.empty()) { diff --git a/src/app/commands/cmd_save_file.cpp b/src/app/commands/cmd_save_file.cpp index 02bbdda60..af36ef65a 100644 --- a/src/app/commands/cmd_save_file.cpp +++ b/src/app/commands/cmd_save_file.cpp @@ -150,6 +150,7 @@ void SaveFileBaseCommand::onLoadParams(const Params& params) m_filename = params.get("filename"); m_filenameFormat = params.get("filename-format"); m_frameTag = params.get("frame-tag"); + m_slice = params.get("slice"); if (params.has_param("from-frame") || params.has_param("to-frame")) { @@ -300,7 +301,7 @@ void SaveFileBaseCommand::saveDocumentInBackground(const Context* context, base::UniquePtr fop( FileOp::createSaveDocumentOperation( context, - FileOpROI(document, m_frameTag, + FileOpROI(document, m_slice, m_frameTag, m_selFrames, m_adjustFramesByFrameTag), document->filename(), m_filenameFormat)); diff --git a/src/app/commands/cmd_save_file.h b/src/app/commands/cmd_save_file.h index 7c86125ca..b3d25c874 100644 --- a/src/app/commands/cmd_save_file.h +++ b/src/app/commands/cmd_save_file.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2016 David Capello +// Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -39,6 +39,7 @@ namespace app { std::string m_filenameFormat; std::string m_selectedFilename; std::string m_frameTag; + std::string m_slice; doc::SelectedFrames m_selFrames; bool m_adjustFramesByFrameTag; }; diff --git a/src/app/file/file.cpp b/src/app/file/file.cpp index d69b73185..0013ff7d4 100644 --- a/src/app/file/file.cpp +++ b/src/app/file/file.cpp @@ -45,7 +45,7 @@ using namespace base; namespace { -void updateXmlPartFromSliceKey(const SliceKey* key, TiXmlElement* xmlPart) +void updateXmlPartFromSliceKey(const doc::SliceKey* key, TiXmlElement* xmlPart) { xmlPart->SetAttribute("x", key->bounds().x); xmlPart->SetAttribute("y", key->bounds().y); @@ -145,8 +145,8 @@ int save_document(Context* context, doc::Document* document) UniquePtr fop( FileOp::createSaveDocumentOperation( context, - FileOpROI(static_cast(document), "", - SelectedFrames(), false), + FileOpROI(static_cast(document), + "", "", SelectedFrames(), false), document->filename(), "")); if (!fop) return -1; @@ -175,19 +175,25 @@ bool is_static_image_format(const std::string& filename) FileOpROI::FileOpROI() : m_document(nullptr) + , m_slice(nullptr) , m_frameTag(nullptr) { } FileOpROI::FileOpROI(const app::Document* doc, + const std::string& sliceName, const std::string& frameTagName, const doc::SelectedFrames& selFrames, const bool adjustByFrameTag) : m_document(doc) + , m_slice(nullptr) , m_frameTag(nullptr) , m_selFrames(selFrames) { if (doc) { + if (!sliceName.empty()) + m_slice = doc->sprite()->slices().getByName(sliceName); + m_frameTag = doc->sprite()->frameTags().getByName(frameTagName); if (m_frameTag) { if (m_selFrames.empty()) @@ -529,6 +535,7 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context, FilenameInfo fnInfo; fnInfo .filename(fn) + .sliceName(fop->m_roi.slice() ? fop->m_roi.slice()->name(): "") .innerTagName(innerTag ? innerTag->name(): "") .outerTagName(outerTag ? outerTag->name(): "") .frame(outputFrame) @@ -584,6 +591,8 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context, // // After this function you must to mark the FileOp as "done" calling // FileOp::done() function. +// +// TODO refactor this code void FileOp::operate(IFileOpProgress* progress) { ASSERT(!isDone()); @@ -604,7 +613,7 @@ void FileOp::operate(IFileOpProgress* progress) frame_t frame(0); Image* old_image = nullptr; - // TODO set_palette for each frame??? + // TODO setPalette for each frame??? auto add_image = [&]() { m_seq.last_cel->data()->setImage(m_seq.image); m_seq.layer->addCel(m_seq.last_cel); @@ -730,8 +739,8 @@ void FileOp::operate(IFileOpProgress* progress) // Create a temporary bitmap m_seq.image.reset(Image::create(sprite->pixelFormat(), - sprite->width(), - sprite->height())); + sprite->width(), + sprite->height())); m_seq.progress_offset = 0.0f; m_seq.progress_fraction = 1.0f / (double)sprite->totalFrames(); @@ -741,7 +750,23 @@ void FileOp::operate(IFileOpProgress* progress) frame_t outputFrame = 0; for (frame_t frame : m_roi.selectedFrames()) { // Draw the "frame" in "m_seq.image" - render.renderSprite(m_seq.image.get(), sprite, frame); + if (m_roi.slice()) { + const SliceKey* key = m_roi.slice()->getByFrame(frame); + if (!key || key->isEmpty()) + continue; // Skip frame because there is no slice key + + m_seq.image.reset( + Image::create(sprite->pixelFormat(), + key->bounds().w, + key->bounds().h)); + + render.renderSprite( + m_seq.image.get(), sprite, frame, + gfx::Clip(gfx::Point(0, 0), key->bounds())); + } + else { + render.renderSprite(m_seq.image.get(), sprite, frame); + } // Setup the palette. sprite->palette(frame)->copyColorsTo(m_seq.palette); diff --git a/src/app/file/file.h b/src/app/file/file.h index 9ce781b99..25545fcc3 100644 --- a/src/app/file/file.h +++ b/src/app/file/file.h @@ -38,6 +38,7 @@ namespace doc { class Layer; class LayerImage; class Palette; + class Slice; class Sprite; } @@ -65,11 +66,13 @@ namespace app { public: FileOpROI(); FileOpROI(const app::Document* doc, + const std::string& sliceName, const std::string& frameTagName, const doc::SelectedFrames& selFrames, const bool adjustByFrameTag); const app::Document* document() const { return m_document; } + doc::Slice* slice() const { return m_slice; } doc::FrameTag* frameTag() const { return m_frameTag; } doc::frame_t fromFrame() const { return m_selFrames.firstFrame(); } doc::frame_t toFrame() const { return m_selFrames.lastFrame(); } @@ -81,6 +84,7 @@ namespace app { private: const app::Document* m_document; + doc::Slice* m_slice; doc::FrameTag* m_frameTag; doc::SelectedFrames m_selFrames; }; diff --git a/src/app/filename_formatter.cpp b/src/app/filename_formatter.cpp index c3b759a2c..110987227 100644 --- a/src/app/filename_formatter.cpp +++ b/src/app/filename_formatter.cpp @@ -88,6 +88,11 @@ bool is_group_in_filename_format(const std::string& format) return (format.find("{group}") != std::string::npos); } +bool is_slice_in_filename_format(const std::string& format) +{ + return (format.find("{slice}") != std::string::npos); +} + std::string filename_formatter( const std::string& format, FilenameInfo& info, @@ -106,6 +111,7 @@ std::string filename_formatter( base::replace_string(output, "{extension}", base::get_file_extension(filename)); base::replace_string(output, "{layer}", info.layerName()); base::replace_string(output, "{group}", info.groupName()); + base::replace_string(output, "{slice}", info.sliceName()); if (replaceFrame) { base::replace_string(output, "{tag}", info.innerTagName()); diff --git a/src/app/filename_formatter.h b/src/app/filename_formatter.h index 28ee2660c..95b1da66a 100644 --- a/src/app/filename_formatter.h +++ b/src/app/filename_formatter.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2001-2016 David Capello +// Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -21,6 +21,7 @@ namespace app { const std::string& groupName() const { return m_groupName; } const std::string& innerTagName() const { return m_innerTagName; } const std::string& outerTagName() const { return m_outerTagName; } + const std::string& sliceName() const { return m_sliceName; } int frame() const { return m_frame; } int tagFrame() const { return m_tagFrame; } @@ -49,6 +50,11 @@ namespace app { return *this; } + FilenameInfo& sliceName(const std::string& value) { + m_sliceName = value; + return *this; + } + FilenameInfo& frame(int value) { m_frame = value; return *this; @@ -65,6 +71,7 @@ namespace app { std::string m_groupName; std::string m_innerTagName; std::string m_outerTagName; + std::string m_sliceName; int m_frame; int m_tagFrame; }; @@ -78,6 +85,7 @@ namespace app { bool is_tag_in_filename_format(const std::string& format); bool is_layer_in_filename_format(const std::string& format); bool is_group_in_filename_format(const std::string& format); + bool is_slice_in_filename_format(const std::string& format); // If "replaceFrame" is false, this function doesn't replace all the // information that depends on the current frame ({frame},