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" />
<!-- Commands not associated to menu items and without shortcuts by default -->
<key command="ExportSpriteSheet">
<param name="source" value="tileset" />
</key>
<key command="NewLayer">
<param name="tilemap" value="true" />
</key>
@ -675,12 +678,14 @@
<separator />
<menu text="@.file_export" group="file_export">
<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 />
<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 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>
<separator id="scripts_menu_separator" />
<menu id="scripts_menu" text="@.file_scripts" group="file_scripts">

View File

@ -307,6 +307,7 @@ DuplicateSprite = Duplicate Sprite
DuplicateView = Duplicate View
Exit = Exit
ExportSpriteSheet = Export Sprite Sheet
ExportTileset = Export Tileset
Eyedropper = Eyedropper
Fill = Fill Selection with Foreground Color
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
ignore_empty = Ignore Empty
ignore_empty_tooltip = Do not include empty/transparent frames in the sprite sheet
source = Source:
layers = Layers:
split_layers = Split Layers
split_layers_tooltip = Generates one sprite for each layer
@ -891,6 +893,7 @@ file_close_all = Close All
file_export = &Export
file_export_as = &Export As...
file_export_sprite_sheet = Export &Sprite Sheet
file_export_tileset = Export &Tileset
file_repeat_last_export = Repeat &Last Export
file_import = &Import
file_import_sprite_sheet = &Import Sprite Sheet

View File

@ -1,5 +1,5 @@
<!-- Aseprite -->
<!-- Copyright (C) 2019 Igara Studio S.A. -->
<!-- Copyright (C) 2019-2022 Igara Studio S.A. -->
<!-- Copyright (C) 2001-2018 David Capello -->
<gui>
<window id="export_sprite_sheet" text="@.title">
@ -50,6 +50,10 @@
<button id="close_sprite_section" icon="window_close_icon" />
</hbox>
<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" />
<combobox id="layers" text="" cell_hspan="2" cell_align="horizontal" />
<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/commands.cpp
commands/convert_layer.cpp
commands/export_tileset.cpp
commands/filters/cmd_brightness_contrast.cpp
commands/filters/cmd_color_curve.cpp
commands/filters/cmd_convolution_matrix.cpp

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// 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_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_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_debug(m_po.add("debug").description("Extreme verbose mode and\ncopy log to desktop"))
#ifdef _WIN32

View File

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

View File

@ -19,25 +19,6 @@
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
{
ASSERT(document);

View File

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

View File

@ -578,6 +578,10 @@ int CliProcessor::process(Context* ctx)
else if (opt == &m_options.oneFrame()) {
cof.oneFrame = true;
}
// --export-tileset
else if (opt == &m_options.exportTileset()) {
cof.exportTileset = true;
}
}
// File names aren't associated to any option
else {
@ -677,12 +681,19 @@ bool CliProcessor::openFile(Context* ctx, CliOpenFile& cof)
if (cof.hasLayersFilter())
filterLayers(doc->sprite(), cof, filteredLayers);
m_exporter->addDocumentSamples(
doc, tag,
cof.splitLayers,
cof.splitTags,
(cof.hasLayersFilter() ? &filteredLayers: nullptr),
(!selFrames.empty() ? &selFrames: nullptr));
if (cof.exportTileset) {
m_exporter->addTilesetsSamples(
doc,
(cof.hasLayersFilter() ? &filteredLayers: 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
#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_access.h"
#include "app/doc.h"
@ -37,7 +37,10 @@
#include "base/string.h"
#include "base/thread.h"
#include "doc/layer.h"
#include "doc/layer_tilemap.h"
#include "doc/tag.h"
#include "doc/tileset.h"
#include "doc/tilesets.h"
#include "fmt/format.h"
#include "ui/message.h"
#include "ui/system.h"
@ -53,37 +56,6 @@ using namespace ui;
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
enum Section {
@ -93,6 +65,11 @@ enum Section {
kSectionOutput,
};
enum Source {
kSource_Sprite,
kSource_Tilesets,
};
enum ConstraintType {
kConstraintType_None,
kConstraintType_Cols,
@ -197,6 +174,7 @@ Doc* generate_sprite_sheet_from_params(
const bool listLayers = params.listLayers();
const bool listTags = params.listTags();
const bool listSlices = params.listSlices();
const bool fromTilesets = params.fromTilesets();
SelectedFrames selFrames;
Tag* tag = calculate_selected_frames(site, tagName, selFrames);
@ -226,10 +204,16 @@ Doc* generate_sprite_sheet_from_params(
}
exporter.reset();
exporter.addDocumentSamples(
doc, tag, splitLayers, splitTags,
!selLayers.empty() ? &selLayers: nullptr,
!selFrames.empty() ? &selFrames: nullptr);
if (fromTilesets)
exporter.addTilesetsSamples(
doc,
!selLayers.empty() ? &selLayers: nullptr);
else {
exporter.addDocumentSamples(
doc, tag, splitLayers, splitTags,
!selLayers.empty() ? &selLayers: nullptr,
!selFrames.empty() ? &selFrames: nullptr);
}
if (saveData) {
if (!filename.empty())
@ -373,6 +357,14 @@ public:
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(
m_sprite, layers(), params.layer());
@ -446,6 +438,7 @@ public:
trimSpriteEnabled()->Click.connect([this]{ onTrimEnabledChange(); });
trimEnabled()->Click.connect([this]{ onTrimEnabledChange(); });
gridTrimEnabled()->Click.connect([this]{ generatePreview(); });
source()->Change.connect([this]{ generatePreview(); });
layers()->Change.connect([this]{ generatePreview(); });
splitLayers()->Click.connect([this]{ onSplitLayersOrFrames(); });
splitTags()->Click.connect([this]{ onSplitLayersOrFrames(); });
@ -536,6 +529,7 @@ public:
params.listLayers (listLayersValue());
params.listTags (listTagsValue());
params.listSlices (listSlicesValue());
params.fromTilesets (source()->getSelectedItemIndex() == int(kSource_Tilesets));
}
private:
@ -1169,16 +1163,8 @@ private:
} // anonymous namespace
class ExportSpriteSheetCommand : public CommandWithNewParams<ExportSpriteSheetParams> {
public:
ExportSpriteSheetCommand();
protected:
bool onEnabled(Context* context) override;
void onExecute(Context* context) override;
};
ExportSpriteSheetCommand::ExportSpriteSheetCommand()
: CommandWithNewParams(CommandId::ExportSpriteSheet(), CmdRecordableFlag)
ExportSpriteSheetCommand::ExportSpriteSheetCommand(const char* id)
: CommandWithNewParams(id, 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(Despeckle)
FOR_EACH_COMMAND(ExportSpriteSheet)
FOR_EACH_COMMAND(ExportTileset)
FOR_EACH_COMMAND(Fill)
FOR_EACH_COMMAND(FlattenLayers)
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
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2022 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -139,7 +139,7 @@ namespace app {
T& params() { return m_params; }
const T& params() const { return m_params; }
private:
protected:
void onResetValues() override {
m_params.resetValues();
}
@ -148,6 +148,7 @@ namespace app {
return m_params.getParam(k);
}
private:
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() = default;
@ -129,6 +136,8 @@ doc::SelectedFrames DocExporter::Item::getSelectedFrames() const
frames.insert(std::clamp(tag->fromFrame(), 0, doc->sprite()->lastFrame()),
std::clamp(tag->toFrame(), 0, doc->sprite()->lastFrame()));
}
else if (isOneImageOnly())
frames.insert(0);
else {
frames.insert(0, doc->sprite()->lastFrame());
}
@ -137,7 +146,10 @@ doc::SelectedFrames DocExporter::Item::getSelectedFrames() const
class DocExporter::Sample {
public:
Sample(Doc* document, Sprite* sprite, SelectedLayers* selLayers,
Sample(Doc* document,
Sprite* sprite,
const ImageRef& image,
SelectedLayers* selLayers,
frame_t frame,
const Tag* tag,
const std::string& filename,
@ -145,6 +157,7 @@ public:
const bool extrude) :
m_document(document),
m_sprite(sprite),
m_image(image),
m_selLayers(selLayers),
m_frame(frame),
m_tag(tag),
@ -153,9 +166,10 @@ public:
m_extrude(extrude),
m_isLinked(false),
m_isDuplicated(false),
m_originalSize(sprite->width(), sprite->height()),
m_trimmedBounds(0, 0, sprite->width(), sprite->height()),
m_inTextureBounds(std::make_shared<gfx::Rect>(0, 0, sprite->width(), sprite->height())) {
m_originalSize(image ? image->width(): sprite->width(),
image ? image->height(): sprite->height()),
m_trimmedBounds(m_originalSize),
m_inTextureBounds(std::make_shared<gfx::Rect>(m_trimmedBounds)) {
}
Doc* document() const { return m_document; }
@ -221,6 +235,11 @@ public:
ImageRef createRender(ImageBufferPtr& imageBuf) {
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(
Image::create(m_sprite->pixelFormat(),
m_trimmedBounds.w,
@ -266,19 +285,32 @@ public:
for (int j=0; j<3; ++j) {
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]));
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 {
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:
Doc* m_document;
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;
frame_t m_frame;
const Tag* m_tag;
@ -347,7 +379,7 @@ public:
int shapePadding,
int& width, int& height,
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 =
(m_type == SpriteSheetType::Columns ||
@ -479,7 +511,7 @@ public:
++i;
}
DX_TRACE("-> SimpleLayoutSamples", width, height);
DX_TRACE("DX: -> SimpleLayoutSamples", width, height);
}
private:
@ -675,7 +707,7 @@ Doc* DocExporter::exportSheet(Context* ctx, base::task_token& token)
// Save the image files.
if (!m_textureFilename.empty()) {
DX_TRACE("DocExporter::exportSheet", m_textureFilename);
DX_TRACE("DX: exportSheet", m_textureFilename);
textureDocument->setFilename(m_textureFilename.c_str());
int ret = save_document(ctx, textureDocument.get());
if (ret == 0)
@ -702,11 +734,18 @@ void DocExporter::addDocument(
const doc::SelectedLayers* selLayers,
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));
}
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(
Doc* doc,
const doc::Tag* thisTag,
@ -715,7 +754,7 @@ int DocExporter::addDocumentSamples(
const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames)
{
DX_TRACE("DocExporter::addDocumentSamples");
DX_TRACE("DX: addDocumentSamples");
std::vector<const Tag*> tags;
@ -804,6 +843,36 @@ int DocExporter::addDocumentSamples(
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,
base::task_token& token)
{
@ -834,24 +903,34 @@ void DocExporter::captureSamples(Samples& samples,
(tag != nullptr)); // Has tag
}
gfx::Rect 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);
gfx::Rect spriteBounds;
// 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;
// This item is only one image (e.g. a tileset tile)
if (item.isOneImageOnly()) {
ASSERT(item.image);
spriteBounds = item.image->bounds();
}
// 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);
Sample sample(
doc, sprite, item.selLayers.get(), frame, innerTag,
filename, m_innerPadding, m_extrude);
doc, sprite, item.image, item.selLayers.get(),
frame, innerTag, filename,
m_innerPadding, m_extrude);
Cel* cel = nullptr;
Cel* link = nullptr;
bool done = false;
@ -892,7 +972,8 @@ void DocExporter::captureSamples(Samples& samples,
// Re-use linked samples
bool alreadyTrimmed = false;
if (link && m_mergeDuplicates) {
if (link && m_mergeDuplicates &&
!item.isOneImageOnly()) {
for (const Sample& other : samples) {
if (token.canceled())
return;
@ -915,7 +996,8 @@ void DocExporter::captureSamples(Samples& samples,
ASSERT(done || (!done && tag));
}
if (!done && (m_ignoreEmptyCels || m_trimCels)) {
if (!done && (m_ignoreEmptyCels || m_trimCels) &&
!item.isOneImageOnly()) {
// Ignore empty cels
if (layer && layer->isImage() && !cel && m_ignoreEmptyCels)
continue;
@ -1306,6 +1388,9 @@ void DocExporter::createDataFile(const Samples& samples,
bool firstTag = true;
for (auto& item : m_documents) {
if (item.isOneImageOnly())
continue;
Doc* doc = item.doc;
Sprite* sprite = doc->sprite();
@ -1337,6 +1422,9 @@ void DocExporter::createDataFile(const Samples& samples,
if (m_listLayers) {
LayerList metaLayers;
for (auto& item : m_documents) {
if (item.isOneImageOnly())
continue;
Doc* doc = item.doc;
Sprite* sprite = doc->sprite();
Layer* root = sprite->root();
@ -1432,6 +1520,9 @@ void DocExporter::createDataFile(const Samples& samples,
bool firstSlice = true;
for (auto& item : m_documents) {
if (item.isOneImageOnly())
continue;
Doc* doc = item.doc;
Sprite* sprite = doc->sprite();

View File

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