mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-16 10:20:50 +00:00
Fix several issues selecting specific layers to export (fix #2084)
Now if we choose a group to export, the children are not automatically exported too, the original visibility state is kept. (Anyway we can still include all those children doing something like "-layer groupName/*" from the CLI.)
This commit is contained in:
parent
fe0563664d
commit
5b782dc27e
@ -34,6 +34,9 @@
|
||||
#include "doc/slice.h"
|
||||
#include "render/dithering_algorithm.h"
|
||||
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
namespace app {
|
||||
|
||||
namespace {
|
||||
@ -65,6 +68,8 @@ bool match_path(const std::string& filter,
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool wildcard = (!a.empty() && a[a.size()-1] == "*");
|
||||
|
||||
// Exclude group itself when all children are excluded. This special
|
||||
// case is only for exclusion because if we leave the group
|
||||
// selected, the propagation of the selection will include all
|
||||
@ -72,46 +77,97 @@ bool match_path(const std::string& filter,
|
||||
if (exclude &&
|
||||
a.size() > 1 &&
|
||||
a.size() == b.size()+1 &&
|
||||
a[a.size()-1] == "*")
|
||||
wildcard) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (a.size() <= b.size());
|
||||
if (exclude || wildcard)
|
||||
return (a.size() <= b.size());
|
||||
else {
|
||||
// Include filters need exact match when there is no wildcard
|
||||
return (a.size() == b.size());
|
||||
}
|
||||
}
|
||||
|
||||
bool filter_layer(const Layer* layer,
|
||||
const std::string& layer_path,
|
||||
bool filter_layer(const std::string& layer_path,
|
||||
const std::vector<std::string>& filters,
|
||||
const bool result)
|
||||
{
|
||||
for (const auto& filter : filters) {
|
||||
if (layer->name() == filter ||
|
||||
match_path(filter, layer_path, !result))
|
||||
if (match_path(filter, layer_path, !result))
|
||||
return result;
|
||||
}
|
||||
return !result;
|
||||
}
|
||||
|
||||
void filter_layers(const LayerList& layers,
|
||||
const CliOpenFile& cof,
|
||||
SelectedLayers& filteredLayers)
|
||||
// If there is one layer with the given name "filter", we can convert
|
||||
// the filter to a full path to the layer (e.g. to match child layers
|
||||
// of a group).
|
||||
std::string convert_filter_to_layer_path_if_possible(
|
||||
const Sprite* sprite,
|
||||
const std::string& filter)
|
||||
{
|
||||
for (Layer* layer : layers) {
|
||||
std::string fullName;
|
||||
std::queue<Layer*> layers;
|
||||
layers.push(sprite->root());
|
||||
|
||||
while (!layers.empty()) {
|
||||
const Layer* layer = layers.front();
|
||||
layers.pop();
|
||||
|
||||
if (layer != sprite->root() &&
|
||||
layer->name() == filter) {
|
||||
if (fullName.empty()) {
|
||||
fullName = get_layer_path(layer);
|
||||
}
|
||||
else {
|
||||
// Two or more layers with the same name (use "filter" as a
|
||||
// general filter, not a specific layer name)
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
if (layer->isGroup()) {
|
||||
for (auto child : static_cast<const LayerGroup*>(layer)->layers())
|
||||
layers.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fullName.empty())
|
||||
return fullName;
|
||||
else
|
||||
return filter;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// static
|
||||
void CliProcessor::FilterLayers(const Sprite* sprite,
|
||||
std::vector<std::string> includes,
|
||||
std::vector<std::string> excludes,
|
||||
SelectedLayers& filteredLayers)
|
||||
{
|
||||
// Convert filters to full paths for the sprite layers if there are
|
||||
// just one layer with the given name.
|
||||
for (auto& include : includes)
|
||||
include = convert_filter_to_layer_path_if_possible(sprite, include);
|
||||
for (auto& exclude : excludes)
|
||||
exclude = convert_filter_to_layer_path_if_possible(sprite, exclude);
|
||||
|
||||
for (Layer* layer : sprite->allLayers()) {
|
||||
auto layer_path = get_layer_path(layer);
|
||||
|
||||
if ((cof.includeLayers.empty() && !layer->isVisibleHierarchy()) ||
|
||||
(!cof.includeLayers.empty() && !filter_layer(layer, layer_path, cof.includeLayers, true)))
|
||||
if ((includes.empty() && !layer->isVisibleHierarchy()) ||
|
||||
(!includes.empty() && !filter_layer(layer_path, includes, true)))
|
||||
continue;
|
||||
|
||||
if (!cof.excludeLayers.empty() &&
|
||||
!filter_layer(layer, layer_path, cof.excludeLayers, false))
|
||||
if (!excludes.empty() &&
|
||||
!filter_layer(layer_path, excludes, false))
|
||||
continue;
|
||||
|
||||
filteredLayers.insert(layer);
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
CliProcessor::CliProcessor(CliDelegate* delegate,
|
||||
const AppOptions& options)
|
||||
: m_delegate(delegate)
|
||||
@ -568,7 +624,7 @@ bool CliProcessor::openFile(Context* ctx, CliOpenFile& cof)
|
||||
|
||||
if (cof.hasLayersFilter()) {
|
||||
SelectedLayers filteredLayers;
|
||||
filter_layers(doc->sprite()->allLayers(), cof, filteredLayers);
|
||||
filterLayers(doc->sprite(), cof, filteredLayers);
|
||||
|
||||
if (cof.splitLayers) {
|
||||
for (Layer* layer : filteredLayers.toAllLayersList()) {
|
||||
@ -638,7 +694,6 @@ void CliProcessor::saveFile(Context* ctx, const CliOpenFile& cof)
|
||||
}
|
||||
|
||||
SelectedLayers filteredLayers;
|
||||
LayerList allLayers = doc->sprite()->allLayers();
|
||||
LayerList layers;
|
||||
// --save-as with --split-layers or --split-tags
|
||||
if (cof.splitLayers) {
|
||||
@ -648,7 +703,7 @@ void CliProcessor::saveFile(Context* ctx, const CliOpenFile& cof)
|
||||
else {
|
||||
// Filter layers
|
||||
if (cof.hasLayersFilter())
|
||||
filter_layers(allLayers, cof, filteredLayers);
|
||||
filterLayers(doc->sprite(), cof, filteredLayers);
|
||||
|
||||
// All visible layers
|
||||
layers.push_back(nullptr);
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2016-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -10,11 +11,16 @@
|
||||
|
||||
#include "app/cli/cli_delegate.h"
|
||||
#include "app/cli/cli_open_file.h"
|
||||
#include "doc/selected_layers.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace doc {
|
||||
class Sprite;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
|
||||
class AppOptions;
|
||||
@ -27,10 +33,27 @@ namespace app {
|
||||
const AppOptions& options);
|
||||
void process(Context* ctx);
|
||||
|
||||
// Public so it can be tested
|
||||
static void FilterLayers(const doc::Sprite* sprite,
|
||||
// By value because these vectors will be modified inside
|
||||
std::vector<std::string> includes,
|
||||
std::vector<std::string> excludes,
|
||||
doc::SelectedLayers& filteredLayers);
|
||||
|
||||
private:
|
||||
bool openFile(Context* ctx, CliOpenFile& cof);
|
||||
void saveFile(Context* ctx, const CliOpenFile& cof);
|
||||
|
||||
void filterLayers(const doc::Sprite* sprite,
|
||||
const CliOpenFile& cof,
|
||||
doc::SelectedLayers& filteredLayers) {
|
||||
CliProcessor::FilterLayers(
|
||||
sprite,
|
||||
cof.includeLayers,
|
||||
cof.excludeLayers,
|
||||
filteredLayers);
|
||||
}
|
||||
|
||||
CliDelegate* m_delegate;
|
||||
const AppOptions& m_options;
|
||||
std::unique_ptr<DocExporter> m_exporter;
|
||||
|
217
src/app/cli/filter_layer_tests.cpp
Normal file
217
src/app/cli/filter_layer_tests.cpp
Normal file
@ -0,0 +1,217 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#include "tests/test.h"
|
||||
|
||||
#include "app/cli/cli_processor.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/sprite.h"
|
||||
|
||||
using namespace app;
|
||||
using namespace doc;
|
||||
|
||||
class FilterLayers : public ::testing::Test {
|
||||
public:
|
||||
FilterLayers()
|
||||
: sprite(ImageSpec(ColorMode::RGB, 32, 32))
|
||||
{
|
||||
a->setName("a");
|
||||
b->setName("b");
|
||||
aa->setName("a");
|
||||
ab->setName("b");
|
||||
ba->setName("a");
|
||||
bb->setName("b");
|
||||
|
||||
sprite.root()->addLayer(a);
|
||||
sprite.root()->addLayer(b);
|
||||
a->addLayer(aa);
|
||||
a->addLayer(ab);
|
||||
b->addLayer(ba);
|
||||
b->addLayer(bb);
|
||||
}
|
||||
|
||||
void filter(
|
||||
std::vector<std::string> includes,
|
||||
std::vector<std::string> excludes)
|
||||
{
|
||||
CliProcessor::FilterLayers(
|
||||
&sprite, std::move(includes), std::move(excludes), sel);
|
||||
}
|
||||
|
||||
Sprite sprite;
|
||||
LayerGroup* a = new LayerGroup(&sprite);
|
||||
LayerGroup* b = new LayerGroup(&sprite);
|
||||
LayerImage* aa = new LayerImage(&sprite);
|
||||
LayerImage* ab = new LayerImage(&sprite);
|
||||
LayerImage* ba = new LayerImage(&sprite);
|
||||
LayerImage* bb = new LayerImage(&sprite);
|
||||
SelectedLayers sel;
|
||||
};
|
||||
|
||||
TEST_F(FilterLayers, Default)
|
||||
{
|
||||
filter({}, {});
|
||||
EXPECT_EQ(6, sel.size());
|
||||
EXPECT_TRUE(sel.contains(a));
|
||||
EXPECT_TRUE(sel.contains(b));
|
||||
EXPECT_TRUE(sel.contains(aa));
|
||||
EXPECT_TRUE(sel.contains(ab));
|
||||
EXPECT_TRUE(sel.contains(ba));
|
||||
EXPECT_TRUE(sel.contains(bb));
|
||||
}
|
||||
|
||||
TEST_F(FilterLayers, DefaultWithHiddenChild)
|
||||
{
|
||||
aa->setVisible(false);
|
||||
|
||||
filter({}, {});
|
||||
EXPECT_EQ(5, sel.size());
|
||||
EXPECT_TRUE(sel.contains(a));
|
||||
EXPECT_TRUE(sel.contains(b));
|
||||
EXPECT_FALSE(sel.contains(aa));
|
||||
EXPECT_TRUE(sel.contains(ab));
|
||||
EXPECT_TRUE(sel.contains(ba));
|
||||
EXPECT_TRUE(sel.contains(bb));
|
||||
}
|
||||
|
||||
TEST_F(FilterLayers, DefaultWithHiddenGroup)
|
||||
{
|
||||
b->setVisible(false);
|
||||
|
||||
filter({}, {});
|
||||
EXPECT_EQ(3, sel.size());
|
||||
EXPECT_TRUE(sel.contains(a));
|
||||
EXPECT_FALSE(sel.contains(b));
|
||||
EXPECT_TRUE(sel.contains(aa));
|
||||
EXPECT_TRUE(sel.contains(ab));
|
||||
EXPECT_FALSE(sel.contains(ba));
|
||||
EXPECT_FALSE(sel.contains(bb));
|
||||
}
|
||||
|
||||
TEST_F(FilterLayers, All)
|
||||
{
|
||||
filter({ "*" }, {});
|
||||
EXPECT_EQ(6, sel.size());
|
||||
EXPECT_TRUE(sel.contains(a));
|
||||
EXPECT_TRUE(sel.contains(b));
|
||||
EXPECT_TRUE(sel.contains(aa));
|
||||
EXPECT_TRUE(sel.contains(ab));
|
||||
EXPECT_TRUE(sel.contains(ba));
|
||||
EXPECT_TRUE(sel.contains(bb));
|
||||
}
|
||||
|
||||
TEST_F(FilterLayers, AllWithHiddenChild)
|
||||
{
|
||||
ab->setVisible(false);
|
||||
|
||||
filter({ "*" }, {});
|
||||
EXPECT_EQ(6, sel.size());
|
||||
EXPECT_TRUE(sel.contains(a));
|
||||
EXPECT_TRUE(sel.contains(b));
|
||||
EXPECT_TRUE(sel.contains(aa));
|
||||
EXPECT_TRUE(sel.contains(ab));
|
||||
EXPECT_TRUE(sel.contains(ba));
|
||||
EXPECT_TRUE(sel.contains(bb));
|
||||
}
|
||||
|
||||
TEST_F(FilterLayers, IncludeGroupAmbiguousNameSelectTopLevel)
|
||||
{
|
||||
filter({ "a" }, {});
|
||||
EXPECT_EQ(1, sel.size());
|
||||
EXPECT_TRUE(sel.contains(a));
|
||||
}
|
||||
|
||||
TEST_F(FilterLayers, IncludeChild)
|
||||
{
|
||||
filter({ "a/a" }, {});
|
||||
EXPECT_EQ(1, sel.size());
|
||||
EXPECT_TRUE(sel.contains(aa));
|
||||
EXPECT_FALSE(sel.contains(a));
|
||||
}
|
||||
|
||||
TEST_F(FilterLayers, IncludeGroupWithHiddenChild)
|
||||
{
|
||||
aa->setVisible(false);
|
||||
|
||||
filter({ "a" }, {});
|
||||
EXPECT_EQ(1, sel.size());
|
||||
EXPECT_TRUE(sel.contains(a));
|
||||
}
|
||||
|
||||
TEST_F(FilterLayers, IncludeChildrenEvenHiddenOne)
|
||||
{
|
||||
aa->setVisible(false);
|
||||
|
||||
filter({ "a/*" }, {});
|
||||
EXPECT_EQ(2, sel.size());
|
||||
EXPECT_TRUE(sel.contains(aa));
|
||||
EXPECT_TRUE(sel.contains(ab));
|
||||
}
|
||||
|
||||
TEST_F(FilterLayers, IncludeHiddenChild)
|
||||
{
|
||||
aa->setName("aa");
|
||||
aa->setVisible(false);
|
||||
|
||||
filter({ "aa" }, {});
|
||||
EXPECT_EQ(1, sel.size());
|
||||
EXPECT_TRUE(sel.contains(aa));
|
||||
}
|
||||
|
||||
TEST_F(FilterLayers, IncludeHiddenChildWithFullPath)
|
||||
{
|
||||
aa->setVisible(false);
|
||||
|
||||
filter({ "a/a" }, {});
|
||||
EXPECT_EQ(1, sel.size());
|
||||
EXPECT_TRUE(sel.contains(aa));
|
||||
}
|
||||
|
||||
TEST_F(FilterLayers, ExcludeAll)
|
||||
{
|
||||
filter({}, { "*" });
|
||||
EXPECT_TRUE(sel.empty());
|
||||
}
|
||||
|
||||
TEST_F(FilterLayers, ExcludeChild)
|
||||
{
|
||||
filter({}, { "a/a" });
|
||||
EXPECT_EQ(5, sel.size());
|
||||
EXPECT_TRUE(sel.contains(a));
|
||||
EXPECT_TRUE(sel.contains(b));
|
||||
EXPECT_FALSE(sel.contains(aa));
|
||||
EXPECT_TRUE(sel.contains(ab));
|
||||
EXPECT_TRUE(sel.contains(ba));
|
||||
EXPECT_TRUE(sel.contains(bb));
|
||||
}
|
||||
|
||||
TEST_F(FilterLayers, ExcludeGroup)
|
||||
{
|
||||
filter({}, { "b" });
|
||||
EXPECT_EQ(3, sel.size());
|
||||
EXPECT_TRUE(sel.contains(a));
|
||||
EXPECT_TRUE(sel.contains(aa));
|
||||
EXPECT_TRUE(sel.contains(ab));
|
||||
EXPECT_FALSE(sel.contains(b));
|
||||
EXPECT_FALSE(sel.contains(ba));
|
||||
EXPECT_FALSE(sel.contains(ba));
|
||||
}
|
||||
|
||||
TEST_F(FilterLayers, IncludeOneGroupAndExcludeOtherGroup)
|
||||
{
|
||||
filter({ "a" }, { "b" });
|
||||
EXPECT_EQ(1, sel.size());
|
||||
EXPECT_TRUE(sel.contains(a));
|
||||
}
|
||||
|
||||
TEST_F(FilterLayers, IncludeAllExcludeOneGroup)
|
||||
{
|
||||
filter({ "*" }, { "b" });
|
||||
EXPECT_EQ(3, sel.size());
|
||||
EXPECT_TRUE(sel.contains(a));
|
||||
EXPECT_TRUE(sel.contains(aa));
|
||||
EXPECT_TRUE(sel.contains(ab));
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
#include "app/doc_exporter.h"
|
||||
#include "app/file/file.h"
|
||||
#include "base/fs.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/sprite.h"
|
||||
|
||||
#include <iostream>
|
||||
@ -111,6 +112,8 @@ void PreviewCliDelegate::saveFile(Context* ctx, const CliOpenFile& cof)
|
||||
<< cof.document->sprite()->height() << "\n";
|
||||
|
||||
showLayersFilter(cof);
|
||||
std::cout << " - Visible Layer:\n";
|
||||
showLayerVisibility(cof.document->sprite()->root(), " ");
|
||||
|
||||
if (cof.hasFrameTag()) {
|
||||
std::cout << " - Frame tag: '" << cof.frameTag << "'\n";
|
||||
@ -241,4 +244,17 @@ void PreviewCliDelegate::showLayersFilter(const CliOpenFile& cof)
|
||||
}
|
||||
}
|
||||
|
||||
void PreviewCliDelegate::showLayerVisibility(const doc::LayerGroup* group,
|
||||
const std::string& indent)
|
||||
{
|
||||
for (auto layer : group->layers()) {
|
||||
if (!layer->isVisible())
|
||||
continue;
|
||||
std::cout << indent << "- " << layer->name() << "\n";
|
||||
if (layer->isGroup())
|
||||
showLayerVisibility(static_cast<const LayerGroup*>(layer),
|
||||
indent + " ");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2016-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -11,6 +11,12 @@
|
||||
|
||||
#include "app/cli/cli_delegate.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace doc {
|
||||
class LayerGroup;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
|
||||
class PreviewCliDelegate : public CliDelegate {
|
||||
@ -34,6 +40,8 @@ namespace app {
|
||||
|
||||
private:
|
||||
void showLayersFilter(const CliOpenFile& cof);
|
||||
void showLayerVisibility(const doc::LayerGroup* group,
|
||||
const std::string& indent);
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
Loading…
x
Reference in New Issue
Block a user