Add "File > Export > Export Tileset" option (#3240)

Now we can export one (or several) tilesets in one sprite sheet (using
the same options that are available in the Export Sprite Sheet dialog,
e.g. like extruding tiles, related to #1982 in some way).

Some changes:

* New "Source" field and fromTilesets param for ExportSpriteSheet
  command
* New ExportTileset command (which acts like ExportSpriteSheet but
  with fromTilesets=true by default)
* Added --export-tileset CLI option
This commit is contained in:
David Capello 2022-08-18 14:56:43 -03:00
parent bdee7d98fe
commit a2f61a3378
16 changed files with 356 additions and 128 deletions

View File

@ -455,6 +455,9 @@
<key command="NewSpriteFromSelection" shortcut="Ctrl+Alt+N" mac="Cmd+Alt+N" /> <key command="NewSpriteFromSelection" shortcut="Ctrl+Alt+N" mac="Cmd+Alt+N" />
<!-- Commands not associated to menu items and without shortcuts by default --> <!-- Commands not associated to menu items and without shortcuts by default -->
<key command="ExportSpriteSheet">
<param name="source" value="tileset" />
</key>
<key command="NewLayer"> <key command="NewLayer">
<param name="tilemap" value="true" /> <param name="tilemap" value="true" />
</key> </key>
@ -675,12 +678,14 @@
<separator /> <separator />
<menu text="@.file_export" group="file_export"> <menu text="@.file_export" group="file_export">
<item command="SaveFileCopyAs" text="@.file_export_as" /> <item command="SaveFileCopyAs" text="@.file_export_as" />
<item command="ExportSpriteSheet" text="@.file_export_sprite_sheet" group="file_export_sub" /> <item command="ExportSpriteSheet" text="@.file_export_sprite_sheet" group="file_export_1" />
<separator /> <separator />
<item command="RepeatLastExport" text="@.file_repeat_last_export" /> <item command="ExportTileset" text="@.file_export_tileset" group="file_export_2" />
<separator />
<item command="RepeatLastExport" text="@.file_repeat_last_export" group="file_export_last" />
</menu> </menu>
<menu text="@.file_import" group="file_import"> <menu text="@.file_import" group="file_import">
<item command="ImportSpriteSheet" text="@.file_import_sprite_sheet" group="file_import_sub" /> <item command="ImportSpriteSheet" text="@.file_import_sprite_sheet" group="file_import_1" />
</menu> </menu>
<separator id="scripts_menu_separator" /> <separator id="scripts_menu_separator" />
<menu id="scripts_menu" text="@.file_scripts" group="file_scripts"> <menu id="scripts_menu" text="@.file_scripts" group="file_scripts">

View File

@ -307,6 +307,7 @@ DuplicateSprite = Duplicate Sprite
DuplicateView = Duplicate View DuplicateView = Duplicate View
Exit = Exit Exit = Exit
ExportSpriteSheet = Export Sprite Sheet ExportSpriteSheet = Export Sprite Sheet
ExportTileset = Export Tileset
Eyedropper = Eyedropper Eyedropper = Eyedropper
Fill = Fill Selection with Foreground Color Fill = Fill Selection with Foreground Color
FitScreen = Fit on Screen FitScreen = Fit on Screen
@ -706,6 +707,7 @@ merge_dups = Merge Duplicates
merge_dups_tooltip = Similar frames can use the same sprite sheet rectangular area merge_dups_tooltip = Similar frames can use the same sprite sheet rectangular area
ignore_empty = Ignore Empty ignore_empty = Ignore Empty
ignore_empty_tooltip = Do not include empty/transparent frames in the sprite sheet ignore_empty_tooltip = Do not include empty/transparent frames in the sprite sheet
source = Source:
layers = Layers: layers = Layers:
split_layers = Split Layers split_layers = Split Layers
split_layers_tooltip = Generates one sprite for each layer split_layers_tooltip = Generates one sprite for each layer
@ -891,6 +893,7 @@ file_close_all = Close All
file_export = &Export file_export = &Export
file_export_as = &Export As... file_export_as = &Export As...
file_export_sprite_sheet = Export &Sprite Sheet file_export_sprite_sheet = Export &Sprite Sheet
file_export_tileset = Export &Tileset
file_repeat_last_export = Repeat &Last Export file_repeat_last_export = Repeat &Last Export
file_import = &Import file_import = &Import
file_import_sprite_sheet = &Import Sprite Sheet file_import_sprite_sheet = &Import Sprite Sheet

View File

@ -1,5 +1,5 @@
<!-- Aseprite --> <!-- Aseprite -->
<!-- Copyright (C) 2019 Igara Studio S.A. --> <!-- Copyright (C) 2019-2022 Igara Studio S.A. -->
<!-- Copyright (C) 2001-2018 David Capello --> <!-- Copyright (C) 2001-2018 David Capello -->
<gui> <gui>
<window id="export_sprite_sheet" text="@.title"> <window id="export_sprite_sheet" text="@.title">
@ -50,6 +50,10 @@
<button id="close_sprite_section" icon="window_close_icon" /> <button id="close_sprite_section" icon="window_close_icon" />
</hbox> </hbox>
<grid id="section_sprite" columns="4" expansive="true"> <grid id="section_sprite" columns="4" expansive="true">
<label text="@.source" />
<combobox id="source" text="" cell_hspan="2" cell_align="horizontal" />
<boxfiller />
<label text="@.layers" /> <label text="@.layers" />
<combobox id="layers" text="" cell_hspan="2" cell_align="horizontal" /> <combobox id="layers" text="" cell_hspan="2" cell_align="horizontal" />
<check id="split_layers" text="@.split_layers" tooltip="@.split_layers_tooltip" tooltip_dir="bottom" /> <check id="split_layers" text="@.split_layers" tooltip="@.split_layers_tooltip" tooltip_dir="bottom" />

View File

@ -570,6 +570,7 @@ add_library(app-lib
commands/command.cpp commands/command.cpp
commands/commands.cpp commands/commands.cpp
commands/convert_layer.cpp commands/convert_layer.cpp
commands/export_tileset.cpp
commands/filters/cmd_brightness_contrast.cpp commands/filters/cmd_brightness_contrast.cpp
commands/filters/cmd_color_curve.cpp commands/filters/cmd_color_curve.cpp
commands/filters/cmd_convolution_matrix.cpp commands/filters/cmd_convolution_matrix.cpp

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A. // Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -73,6 +73,7 @@ AppOptions::AppOptions(int argc, const char* argv[])
, m_listTags(m_po.add("list-tags").description("List tags of the next given sprite\nor include frame tags in JSON data")) , m_listTags(m_po.add("list-tags").description("List tags of the next given sprite\nor include frame tags in JSON data"))
, m_listSlices(m_po.add("list-slices").description("List slices of the next given sprite\nor include slices in JSON data")) , m_listSlices(m_po.add("list-slices").description("List slices of the next given sprite\nor include slices in JSON data"))
, m_oneFrame(m_po.add("oneframe").description("Load just the first frame")) , m_oneFrame(m_po.add("oneframe").description("Load just the first frame"))
, m_exportTileset(m_po.add("export-tileset").description("Export only tilesets from visible tilemap layers"))
, m_verbose(m_po.add("verbose").mnemonic('v').description("Explain what is being done")) , m_verbose(m_po.add("verbose").mnemonic('v').description("Explain what is being done"))
, m_debug(m_po.add("debug").description("Extreme verbose mode and\ncopy log to desktop")) , m_debug(m_po.add("debug").description("Extreme verbose mode and\ncopy log to desktop"))
#ifdef _WIN32 #ifdef _WIN32

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A. // Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -89,6 +89,7 @@ public:
const Option& listTags() const { return m_listTags; } const Option& listTags() const { return m_listTags; }
const Option& listSlices() const { return m_listSlices; } const Option& listSlices() const { return m_listSlices; }
const Option& oneFrame() const { return m_oneFrame; } const Option& oneFrame() const { return m_oneFrame; }
const Option& exportTileset() const { return m_exportTileset; }
bool hasExporterParams() const; bool hasExporterParams() const;
#ifdef _WIN32 #ifdef _WIN32
@ -155,6 +156,7 @@ private:
Option& m_listTags; Option& m_listTags;
Option& m_listSlices; Option& m_listSlices;
Option& m_oneFrame; Option& m_oneFrame;
Option& m_exportTileset;
Option& m_verbose; Option& m_verbose;
Option& m_debug; Option& m_debug;

View File

@ -19,25 +19,6 @@
namespace app { namespace app {
CliOpenFile::CliOpenFile()
{
document = nullptr;
fromFrame = -1;
toFrame = -1;
splitLayers = false;
splitTags = false;
splitSlices = false;
allLayers = false;
listLayers = false;
listTags = false;
listSlices = false;
ignoreEmpty = false;
trim = false;
trimByGrid = false;
oneFrame = false;
crop = gfx::Rect();
}
FileOpROI CliOpenFile::roi() const FileOpROI CliOpenFile::roi() const
{ {
ASSERT(document); ASSERT(document);

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2016-2017 David Capello // Copyright (C) 2016-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -21,29 +21,29 @@ namespace app {
class FileOpROI; class FileOpROI;
struct CliOpenFile { struct CliOpenFile {
Doc* document; Doc* document = nullptr;
std::string filename; std::string filename;
std::string filenameFormat; std::string filenameFormat;
std::string tag; std::string tag;
std::string slice; std::string slice;
std::vector<std::string> includeLayers; std::vector<std::string> includeLayers;
std::vector<std::string> excludeLayers; std::vector<std::string> excludeLayers;
doc::frame_t fromFrame, toFrame; doc::frame_t fromFrame = -1;
bool splitLayers; doc::frame_t toFrame = -1;
bool splitTags; bool splitLayers = false;
bool splitSlices; bool splitTags = false;
bool allLayers; bool splitSlices = false;
bool listLayers; bool allLayers = false;
bool listTags; bool listLayers = false;
bool listSlices; bool listTags = false;
bool ignoreEmpty; bool listSlices = false;
bool trim; bool ignoreEmpty = false;
bool trimByGrid; bool trim = false;
bool oneFrame; bool trimByGrid = false;
bool oneFrame = false;
bool exportTileset = false;
gfx::Rect crop; gfx::Rect crop;
CliOpenFile();
bool hasTag() const { bool hasTag() const {
return (!tag.empty()); return (!tag.empty());
} }

View File

@ -578,6 +578,10 @@ int CliProcessor::process(Context* ctx)
else if (opt == &m_options.oneFrame()) { else if (opt == &m_options.oneFrame()) {
cof.oneFrame = true; cof.oneFrame = true;
} }
// --export-tileset
else if (opt == &m_options.exportTileset()) {
cof.exportTileset = true;
}
} }
// File names aren't associated to any option // File names aren't associated to any option
else { else {
@ -677,12 +681,19 @@ bool CliProcessor::openFile(Context* ctx, CliOpenFile& cof)
if (cof.hasLayersFilter()) if (cof.hasLayersFilter())
filterLayers(doc->sprite(), cof, filteredLayers); filterLayers(doc->sprite(), cof, filteredLayers);
m_exporter->addDocumentSamples( if (cof.exportTileset) {
doc, tag, m_exporter->addTilesetsSamples(
cof.splitLayers, doc,
cof.splitTags, (cof.hasLayersFilter() ? &filteredLayers: nullptr));
(cof.hasLayersFilter() ? &filteredLayers: nullptr), }
(!selFrames.empty() ? &selFrames: nullptr)); else {
m_exporter->addDocumentSamples(
doc, tag,
cof.splitLayers,
cof.splitTags,
(cof.hasLayersFilter() ? &filteredLayers: nullptr),
(!selFrames.empty() ? &selFrames: nullptr));
}
} }
} }

View File

@ -10,7 +10,7 @@
#endif #endif
#include "app/app.h" #include "app/app.h"
#include "app/commands/new_params.h" #include "app/commands/cmd_export_sprite_sheet.h"
#include "app/context.h" #include "app/context.h"
#include "app/context_access.h" #include "app/context_access.h"
#include "app/doc.h" #include "app/doc.h"
@ -37,7 +37,10 @@
#include "base/string.h" #include "base/string.h"
#include "base/thread.h" #include "base/thread.h"
#include "doc/layer.h" #include "doc/layer.h"
#include "doc/layer_tilemap.h"
#include "doc/tag.h" #include "doc/tag.h"
#include "doc/tileset.h"
#include "doc/tilesets.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "ui/message.h" #include "ui/message.h"
#include "ui/system.h" #include "ui/system.h"
@ -53,37 +56,6 @@ using namespace ui;
namespace { namespace {
struct ExportSpriteSheetParams : public NewParams {
Param<bool> ui { this, true, "ui" };
Param<bool> askOverwrite { this, true, { "askOverwrite", "ask-overwrite" } };
Param<app::SpriteSheetType> type { this, app::SpriteSheetType::None, "type" };
Param<int> columns { this, 0, "columns" };
Param<int> rows { this, 0, "rows" };
Param<int> width { this, 0, "width" };
Param<int> height { this, 0, "height" };
Param<std::string> textureFilename { this, std::string(), "textureFilename" };
Param<std::string> dataFilename { this, std::string(), "dataFilename" };
Param<SpriteSheetDataFormat> dataFormat { this, SpriteSheetDataFormat::Default, "dataFormat" };
Param<std::string> filenameFormat { this, std::string(), "filenameFormat" };
Param<int> borderPadding { this, 0, "borderPadding" };
Param<int> shapePadding { this, 0, "shapePadding" };
Param<int> innerPadding { this, 0, "innerPadding" };
Param<bool> trimSprite { this, false, "trimSprite" };
Param<bool> trim { this, false, "trim" };
Param<bool> trimByGrid { this, false, "trimByGrid" };
Param<bool> extrude { this, false, "extrude" };
Param<bool> ignoreEmpty { this, false, "ignoreEmpty" };
Param<bool> mergeDuplicates { this, false, "mergeDuplicates" };
Param<bool> openGenerated { this, false, "openGenerated" };
Param<std::string> layer { this, std::string(), "layer" };
Param<std::string> tag { this, std::string(), "tag" };
Param<bool> splitLayers { this, false, "splitLayers" };
Param<bool> splitTags { this, false, "splitTags" };
Param<bool> listLayers { this, true, "listLayers" };
Param<bool> listTags { this, true, "listTags" };
Param<bool> listSlices { this, true, "listSlices" };
};
#ifdef ENABLE_UI #ifdef ENABLE_UI
enum Section { enum Section {
@ -93,6 +65,11 @@ enum Section {
kSectionOutput, kSectionOutput,
}; };
enum Source {
kSource_Sprite,
kSource_Tilesets,
};
enum ConstraintType { enum ConstraintType {
kConstraintType_None, kConstraintType_None,
kConstraintType_Cols, kConstraintType_Cols,
@ -197,6 +174,7 @@ Doc* generate_sprite_sheet_from_params(
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();
const bool fromTilesets = params.fromTilesets();
SelectedFrames selFrames; SelectedFrames selFrames;
Tag* tag = calculate_selected_frames(site, tagName, selFrames); Tag* tag = calculate_selected_frames(site, tagName, selFrames);
@ -226,10 +204,16 @@ Doc* generate_sprite_sheet_from_params(
} }
exporter.reset(); exporter.reset();
exporter.addDocumentSamples( if (fromTilesets)
doc, tag, splitLayers, splitTags, exporter.addTilesetsSamples(
!selLayers.empty() ? &selLayers: nullptr, doc,
!selFrames.empty() ? &selFrames: nullptr); !selLayers.empty() ? &selLayers: nullptr);
else {
exporter.addDocumentSamples(
doc, tag, splitLayers, splitTags,
!selLayers.empty() ? &selLayers: nullptr,
!selFrames.empty() ? &selFrames: nullptr);
}
if (saveData) { if (saveData) {
if (!filename.empty()) if (!filename.empty())
@ -373,6 +357,14 @@ public:
break; break;
} }
static_assert(kSource_Sprite == 0 &&
kSource_Tilesets == 1,
"Source enum has changed");
source()->addItem(new ListItem("Sprite"));
source()->addItem(new ListItem("Tilesets"));
if (params.fromTilesets())
source()->setSelectedItemIndex(int(kSource_Tilesets));
fill_layers_combobox( fill_layers_combobox(
m_sprite, layers(), params.layer()); m_sprite, layers(), params.layer());
@ -446,6 +438,7 @@ public:
trimSpriteEnabled()->Click.connect([this]{ onTrimEnabledChange(); }); trimSpriteEnabled()->Click.connect([this]{ onTrimEnabledChange(); });
trimEnabled()->Click.connect([this]{ onTrimEnabledChange(); }); trimEnabled()->Click.connect([this]{ onTrimEnabledChange(); });
gridTrimEnabled()->Click.connect([this]{ generatePreview(); }); gridTrimEnabled()->Click.connect([this]{ generatePreview(); });
source()->Change.connect([this]{ generatePreview(); });
layers()->Change.connect([this]{ generatePreview(); }); layers()->Change.connect([this]{ generatePreview(); });
splitLayers()->Click.connect([this]{ onSplitLayersOrFrames(); }); splitLayers()->Click.connect([this]{ onSplitLayersOrFrames(); });
splitTags()->Click.connect([this]{ onSplitLayersOrFrames(); }); splitTags()->Click.connect([this]{ onSplitLayersOrFrames(); });
@ -536,6 +529,7 @@ public:
params.listLayers (listLayersValue()); params.listLayers (listLayersValue());
params.listTags (listTagsValue()); params.listTags (listTagsValue());
params.listSlices (listSlicesValue()); params.listSlices (listSlicesValue());
params.fromTilesets (source()->getSelectedItemIndex() == int(kSource_Tilesets));
} }
private: private:
@ -1169,16 +1163,8 @@ private:
} // anonymous namespace } // anonymous namespace
class ExportSpriteSheetCommand : public CommandWithNewParams<ExportSpriteSheetParams> { ExportSpriteSheetCommand::ExportSpriteSheetCommand(const char* id)
public: : CommandWithNewParams(id, CmdRecordableFlag)
ExportSpriteSheetCommand();
protected:
bool onEnabled(Context* context) override;
void onExecute(Context* context) override;
};
ExportSpriteSheetCommand::ExportSpriteSheetCommand()
: CommandWithNewParams(CommandId::ExportSpriteSheet(), CmdRecordableFlag)
{ {
} }

View File

@ -0,0 +1,62 @@
// Aseprite
// Copyright (C) 2022 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_COMMANDS_CMD_EXPORT_SPRITE_SHEET_H_INCLUDED
#define APP_COMMANDS_CMD_EXPORT_SPRITE_SHEET_H_INCLUDED
#pragma once
#include "app/commands/new_params.h"
#include "app/sprite_sheet_data_format.h"
#include "app/sprite_sheet_type.h"
#include <limits>
#include <sstream>
namespace app {
struct ExportSpriteSheetParams : public NewParams {
Param<bool> ui { this, true, "ui" };
Param<bool> askOverwrite { this, true, { "askOverwrite", "ask-overwrite" } };
Param<app::SpriteSheetType> type { this, app::SpriteSheetType::None, "type" };
Param<int> columns { this, 0, "columns" };
Param<int> rows { this, 0, "rows" };
Param<int> width { this, 0, "width" };
Param<int> height { this, 0, "height" };
Param<std::string> textureFilename { this, std::string(), "textureFilename" };
Param<std::string> dataFilename { this, std::string(), "dataFilename" };
Param<SpriteSheetDataFormat> dataFormat { this, SpriteSheetDataFormat::Default, "dataFormat" };
Param<std::string> filenameFormat { this, std::string(), "filenameFormat" };
Param<int> borderPadding { this, 0, "borderPadding" };
Param<int> shapePadding { this, 0, "shapePadding" };
Param<int> innerPadding { this, 0, "innerPadding" };
Param<bool> trimSprite { this, false, "trimSprite" };
Param<bool> trim { this, false, "trim" };
Param<bool> trimByGrid { this, false, "trimByGrid" };
Param<bool> extrude { this, false, "extrude" };
Param<bool> ignoreEmpty { this, false, "ignoreEmpty" };
Param<bool> mergeDuplicates { this, false, "mergeDuplicates" };
Param<bool> openGenerated { this, false, "openGenerated" };
Param<std::string> layer { this, std::string(), "layer" };
Param<std::string> tag { this, std::string(), "tag" };
Param<bool> splitLayers { this, false, "splitLayers" };
Param<bool> splitTags { this, false, "splitTags" };
Param<bool> listLayers { this, true, "listLayers" };
Param<bool> listTags { this, true, "listTags" };
Param<bool> listSlices { this, true, "listSlices" };
Param<bool> fromTilesets { this, false, "fromTilesets" };
};
class ExportSpriteSheetCommand : public CommandWithNewParams<ExportSpriteSheetParams> {
public:
ExportSpriteSheetCommand(const char* id = CommandId::ExportSpriteSheet());
protected:
bool onEnabled(Context* context) override;
void onExecute(Context* context) override;
};
} // namespace app
#endif // APP_COMMANDS_CMD_EXPORT_SPRITE_SHEET_H_INCLUDED

View File

@ -21,6 +21,7 @@ FOR_EACH_COMMAND(CopyTiles)
FOR_EACH_COMMAND(CropSprite) FOR_EACH_COMMAND(CropSprite)
FOR_EACH_COMMAND(Despeckle) FOR_EACH_COMMAND(Despeckle)
FOR_EACH_COMMAND(ExportSpriteSheet) FOR_EACH_COMMAND(ExportSpriteSheet)
FOR_EACH_COMMAND(ExportTileset)
FOR_EACH_COMMAND(Fill) FOR_EACH_COMMAND(Fill)
FOR_EACH_COMMAND(FlattenLayers) FOR_EACH_COMMAND(FlattenLayers)
FOR_EACH_COMMAND(Flip) FOR_EACH_COMMAND(Flip)

View File

@ -0,0 +1,65 @@
// Aseprite
// Copyright (C) 2022 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/commands/cmd_export_sprite_sheet.h"
#include "app/commands/command_ids.h"
#include "app/context.h"
#include "app/site.h"
#include "app/ui/layer_frame_comboboxes.h"
#include "doc/layer.h"
namespace app {
class ExportTilesetCommand : public ExportSpriteSheetCommand {
public:
ExportTilesetCommand();
protected:
void onResetValues() override;
bool onEnabled(Context* context) override;
void onExecute(Context* context) override;
};
ExportTilesetCommand::ExportTilesetCommand()
: ExportSpriteSheetCommand(CommandId::ExportTileset())
{
}
void ExportTilesetCommand::onResetValues()
{
ExportSpriteSheetCommand::onResetValues();
// Default values for Export Tileset
params().fromTilesets(true);
params().layer(kSelectedLayers);
params().dataFormat(SpriteSheetDataFormat::JsonArray);
}
bool ExportTilesetCommand::onEnabled(Context* ctx)
{
if (ExportSpriteSheetCommand::onEnabled(ctx) &&
ctx->checkFlags(ContextFlags::HasActiveLayer)) {
Site site = ctx->activeSite();
if (site.layer() && site.layer()->isTilemap())
return true;
}
return false;
}
void ExportTilesetCommand::onExecute(Context* ctx)
{
ExportSpriteSheetCommand::onExecute(ctx);
}
Command* CommandFactory::createExportTilesetCommand()
{
return new ExportTilesetCommand;
}
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2019-2022 Igara Studio S.A.
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -139,7 +139,7 @@ namespace app {
T& params() { return m_params; } T& params() { return m_params; }
const T& params() const { return m_params; } const T& params() const { return m_params; }
private: protected:
void onResetValues() override { void onResetValues() override {
m_params.resetValues(); m_params.resetValues();
} }
@ -148,6 +148,7 @@ namespace app {
return m_params.getParam(k); return m_params.getParam(k);
} }
private:
T m_params; T m_params;
}; };

View File

@ -104,6 +104,13 @@ DocExporter::Item::Item(Doc* doc,
{ {
} }
DocExporter::Item::Item(Doc* doc,
const doc::ImageRef& image)
: doc(doc)
, image(image)
{
}
DocExporter::Item::Item(Item&& other) = default; DocExporter::Item::Item(Item&& other) = default;
DocExporter::Item::~Item() = default; DocExporter::Item::~Item() = default;
@ -129,6 +136,8 @@ doc::SelectedFrames DocExporter::Item::getSelectedFrames() const
frames.insert(std::clamp(tag->fromFrame(), 0, doc->sprite()->lastFrame()), frames.insert(std::clamp(tag->fromFrame(), 0, doc->sprite()->lastFrame()),
std::clamp(tag->toFrame(), 0, doc->sprite()->lastFrame())); std::clamp(tag->toFrame(), 0, doc->sprite()->lastFrame()));
} }
else if (isOneImageOnly())
frames.insert(0);
else { else {
frames.insert(0, doc->sprite()->lastFrame()); frames.insert(0, doc->sprite()->lastFrame());
} }
@ -137,7 +146,10 @@ doc::SelectedFrames DocExporter::Item::getSelectedFrames() const
class DocExporter::Sample { class DocExporter::Sample {
public: public:
Sample(Doc* document, Sprite* sprite, SelectedLayers* selLayers, Sample(Doc* document,
Sprite* sprite,
const ImageRef& image,
SelectedLayers* selLayers,
frame_t frame, frame_t frame,
const Tag* tag, const Tag* tag,
const std::string& filename, const std::string& filename,
@ -145,6 +157,7 @@ public:
const bool extrude) : const bool extrude) :
m_document(document), m_document(document),
m_sprite(sprite), m_sprite(sprite),
m_image(image),
m_selLayers(selLayers), m_selLayers(selLayers),
m_frame(frame), m_frame(frame),
m_tag(tag), m_tag(tag),
@ -153,9 +166,10 @@ public:
m_extrude(extrude), m_extrude(extrude),
m_isLinked(false), m_isLinked(false),
m_isDuplicated(false), m_isDuplicated(false),
m_originalSize(sprite->width(), sprite->height()), m_originalSize(image ? image->width(): sprite->width(),
m_trimmedBounds(0, 0, sprite->width(), sprite->height()), image ? image->height(): sprite->height()),
m_inTextureBounds(std::make_shared<gfx::Rect>(0, 0, sprite->width(), sprite->height())) { m_trimmedBounds(m_originalSize),
m_inTextureBounds(std::make_shared<gfx::Rect>(m_trimmedBounds)) {
} }
Doc* document() const { return m_document; } Doc* document() const { return m_document; }
@ -221,6 +235,11 @@ public:
ImageRef createRender(ImageBufferPtr& imageBuf) { ImageRef createRender(ImageBufferPtr& imageBuf) {
ASSERT(m_sprite); ASSERT(m_sprite);
// We use the m_image as it is, it doesn't require a special
// render.
if (m_image)
return m_image;
ImageRef render( ImageRef render(
Image::create(m_sprite->pixelFormat(), Image::create(m_sprite->pixelFormat(),
m_trimmedBounds.w, m_trimmedBounds.w,
@ -266,19 +285,32 @@ public:
for (int j=0; j<3; ++j) { for (int j=0; j<3; ++j) {
for (int i=0; i<3; ++i) { for (int i=0; i<3; ++i) {
gfx::Clip clip(x+dx[i], y+dy[j], gfx::RectT<int>(srcx[i], srcy[j], szx[i], szy[j])); gfx::Clip clip(x+dx[i], y+dy[j], gfx::RectT<int>(srcx[i], srcy[j], szx[i], szy[j]));
render.renderSprite(dst, m_sprite, m_frame, clip); if (m_image) {
dst->copy(m_image.get(), clip);
}
else {
render.renderSprite(dst, m_sprite, m_frame, clip);
}
} }
} }
} }
else { else {
gfx::Clip clip(x, y, m_trimmedBounds); gfx::Clip clip(x, y, m_trimmedBounds);
render.renderSprite(dst, m_sprite, m_frame, clip); if (m_image) {
dst->copy(m_image.get(), clip);
}
else {
render.renderSprite(dst, m_sprite, m_frame, clip);
}
} }
} }
private: private:
Doc* m_document; Doc* m_document;
Sprite* m_sprite; Sprite* m_sprite;
// In case that this Sample references just one image to export
// (e.g. like a Tileset tile image) this can be != nullptr.
ImageRef m_image;
SelectedLayers* m_selLayers; SelectedLayers* m_selLayers;
frame_t m_frame; frame_t m_frame;
const Tag* m_tag; const Tag* m_tag;
@ -347,7 +379,7 @@ public:
int shapePadding, int shapePadding,
int& width, int& height, int& width, int& height,
base::task_token& token) override { base::task_token& token) override {
DX_TRACE("SimpleLayoutSamples type", (int)m_type, width, height); DX_TRACE("DX: SimpleLayoutSamples type", (int)m_type, width, height);
const bool breakBands = const bool breakBands =
(m_type == SpriteSheetType::Columns || (m_type == SpriteSheetType::Columns ||
@ -479,7 +511,7 @@ public:
++i; ++i;
} }
DX_TRACE("-> SimpleLayoutSamples", width, height); DX_TRACE("DX: -> SimpleLayoutSamples", width, height);
} }
private: private:
@ -675,7 +707,7 @@ Doc* DocExporter::exportSheet(Context* ctx, base::task_token& token)
// Save the image files. // Save the image files.
if (!m_textureFilename.empty()) { if (!m_textureFilename.empty()) {
DX_TRACE("DocExporter::exportSheet", m_textureFilename); DX_TRACE("DX: exportSheet", m_textureFilename);
textureDocument->setFilename(m_textureFilename.c_str()); textureDocument->setFilename(m_textureFilename.c_str());
int ret = save_document(ctx, textureDocument.get()); int ret = save_document(ctx, textureDocument.get());
if (ret == 0) if (ret == 0)
@ -702,11 +734,18 @@ void DocExporter::addDocument(
const doc::SelectedLayers* selLayers, const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames) const doc::SelectedFrames* selFrames)
{ {
DX_TRACE("DocExporter::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));
} }
void DocExporter::addImage(
Doc* doc,
const doc::ImageRef& image)
{
DX_TRACE("DX: addImage doc=", doc, "image=", image.get());
m_documents.push_back(Item(doc, image));
}
int DocExporter::addDocumentSamples( int DocExporter::addDocumentSamples(
Doc* doc, Doc* doc,
const doc::Tag* thisTag, const doc::Tag* thisTag,
@ -715,7 +754,7 @@ int DocExporter::addDocumentSamples(
const doc::SelectedLayers* selLayers, const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames) const doc::SelectedFrames* selFrames)
{ {
DX_TRACE("DocExporter::addDocumentSamples"); DX_TRACE("DX: addDocumentSamples");
std::vector<const Tag*> tags; std::vector<const Tag*> tags;
@ -804,6 +843,36 @@ int DocExporter::addDocumentSamples(
return std::max(1, items); return std::max(1, items);
} }
int DocExporter::addTilesetsSamples(
Doc* doc,
const doc::SelectedLayers* selLayers)
{
LayerList layers;
if (selLayers)
layers = selLayers->toAllLayersList();
else
layers = doc->sprite()->allVisibleLayers();
std::set<doc::ObjectId> alreadyExported;
int items = 0;
for (auto& layer : layers) {
if (layer->isTilemap()) {
Tileset* ts = dynamic_cast<LayerTilemap*>(layer)->tileset();
if (alreadyExported.find(ts->id()) == alreadyExported.end()) {
for (const ImageRef& image : *ts) {
addImage(doc, image);
++items;
}
alreadyExported.insert(ts->id());
}
}
}
DX_TRACE("DX: addTilesetsSamples items=", items);
return items;
}
void DocExporter::captureSamples(Samples& samples, void DocExporter::captureSamples(Samples& samples,
base::task_token& token) base::task_token& token)
{ {
@ -834,24 +903,34 @@ void DocExporter::captureSamples(Samples& samples,
(tag != nullptr)); // Has tag (tag != nullptr)); // Has tag
} }
gfx::Rect spriteBounds = sprite->bounds(); gfx::Rect spriteBounds;
if (m_trimSprite) {
if (m_cache.spriteId == sprite->id() &&
m_cache.spriteVer == sprite->version() &&
m_cache.trimmedByGrid == m_trimByGrid) {
spriteBounds = m_cache.trimmedBounds;
}
else {
spriteBounds = get_trimmed_bounds(sprite, m_trimByGrid);
if (spriteBounds.isEmpty())
spriteBounds = gfx::Rect(0, 0, 1, 1);
// Cache trimmed bounds so we don't have to recalculate them // This item is only one image (e.g. a tileset tile)
// in the next iteration/preview. if (item.isOneImageOnly()) {
m_cache.spriteId = sprite->id(); ASSERT(item.image);
m_cache.spriteVer = sprite->version(); spriteBounds = item.image->bounds();
m_cache.trimmedByGrid = m_trimByGrid; }
m_cache.trimmedBounds = spriteBounds; // This item comes from the sprite canvas
else {
spriteBounds = sprite->bounds();
if (m_trimSprite) {
if (m_cache.spriteId == sprite->id() &&
m_cache.spriteVer == sprite->version() &&
m_cache.trimmedByGrid == m_trimByGrid) {
spriteBounds = m_cache.trimmedBounds;
}
else {
spriteBounds = get_trimmed_bounds(sprite, m_trimByGrid);
if (spriteBounds.isEmpty())
spriteBounds = gfx::Rect(0, 0, 1, 1);
// Cache trimmed bounds so we don't have to recalculate them
// in the next iteration/preview.
m_cache.spriteId = sprite->id();
m_cache.spriteVer = sprite->version();
m_cache.trimmedByGrid = m_trimByGrid;
m_cache.trimmedBounds = spriteBounds;
}
} }
} }
@ -878,8 +957,9 @@ void DocExporter::captureSamples(Samples& samples,
std::string filename = filename_formatter(format, fnInfo); std::string filename = filename_formatter(format, fnInfo);
Sample sample( Sample sample(
doc, sprite, item.selLayers.get(), frame, innerTag, doc, sprite, item.image, item.selLayers.get(),
filename, m_innerPadding, m_extrude); frame, innerTag, filename,
m_innerPadding, m_extrude);
Cel* cel = nullptr; Cel* cel = nullptr;
Cel* link = nullptr; Cel* link = nullptr;
bool done = false; bool done = false;
@ -892,7 +972,8 @@ void DocExporter::captureSamples(Samples& samples,
// Re-use linked samples // Re-use linked samples
bool alreadyTrimmed = false; bool alreadyTrimmed = false;
if (link && m_mergeDuplicates) { if (link && m_mergeDuplicates &&
!item.isOneImageOnly()) {
for (const Sample& other : samples) { for (const Sample& other : samples) {
if (token.canceled()) if (token.canceled())
return; return;
@ -915,7 +996,8 @@ void DocExporter::captureSamples(Samples& samples,
ASSERT(done || (!done && tag)); ASSERT(done || (!done && tag));
} }
if (!done && (m_ignoreEmptyCels || m_trimCels)) { if (!done && (m_ignoreEmptyCels || m_trimCels) &&
!item.isOneImageOnly()) {
// Ignore empty cels // Ignore empty cels
if (layer && layer->isImage() && !cel && m_ignoreEmptyCels) if (layer && layer->isImage() && !cel && m_ignoreEmptyCels)
continue; continue;
@ -1306,6 +1388,9 @@ void DocExporter::createDataFile(const Samples& samples,
bool firstTag = true; bool firstTag = true;
for (auto& item : m_documents) { for (auto& item : m_documents) {
if (item.isOneImageOnly())
continue;
Doc* doc = item.doc; Doc* doc = item.doc;
Sprite* sprite = doc->sprite(); Sprite* sprite = doc->sprite();
@ -1337,6 +1422,9 @@ void DocExporter::createDataFile(const Samples& samples,
if (m_listLayers) { if (m_listLayers) {
LayerList metaLayers; LayerList metaLayers;
for (auto& item : m_documents) { for (auto& item : m_documents) {
if (item.isOneImageOnly())
continue;
Doc* doc = item.doc; Doc* doc = item.doc;
Sprite* sprite = doc->sprite(); Sprite* sprite = doc->sprite();
Layer* root = sprite->root(); Layer* root = sprite->root();
@ -1432,6 +1520,9 @@ void DocExporter::createDataFile(const Samples& samples,
bool firstSlice = true; bool firstSlice = true;
for (auto& item : m_documents) { for (auto& item : m_documents) {
if (item.isOneImageOnly())
continue;
Doc* doc = item.doc; Doc* doc = item.doc;
Sprite* sprite = doc->sprite(); Sprite* sprite = doc->sprite();

View File

@ -14,6 +14,7 @@
#include "base/disable_copying.h" #include "base/disable_copying.h"
#include "base/task.h" #include "base/task.h"
#include "doc/frame.h" #include "doc/frame.h"
#include "doc/image_ref.h"
#include "doc/image_buffer.h" #include "doc/image_buffer.h"
#include "doc/object_id.h" #include "doc/object_id.h"
#include "doc/object_version.h" #include "doc/object_version.h"
@ -82,6 +83,10 @@ namespace app {
const doc::SelectedLayers* selLayers, const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames); const doc::SelectedFrames* selFrames);
void addImage(
Doc* doc,
const doc::ImageRef& image);
int addDocumentSamples( int addDocumentSamples(
Doc* doc, Doc* doc,
const doc::Tag* tag, const doc::Tag* tag,
@ -90,6 +95,10 @@ namespace app {
const doc::SelectedLayers* selLayers, const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames); const doc::SelectedFrames* selFrames);
int addTilesetsSamples(
Doc* doc,
const doc::SelectedLayers* selLayers);
Doc* exportSheet(Context* ctx, base::task_token& token); Doc* exportSheet(Context* ctx, base::task_token& token);
gfx::Size calculateSheetSize(); gfx::Size calculateSheetSize();
@ -121,11 +130,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;
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);
Item(Doc* doc,
const doc::ImageRef& image);
Item(Item&& other); Item(Item&& other);
~Item(); ~Item();
@ -135,6 +147,8 @@ namespace app {
int frames() const; int frames() const;
doc::SelectedFrames getSelectedFrames() const; doc::SelectedFrames getSelectedFrames() const;
bool isOneImageOnly() const { return image != nullptr; }
}; };
typedef std::vector<Item> Items; typedef std::vector<Item> Items;