Merge branch 'export' into beta

This commit is contained in:
David Capello 2022-08-19 18:05:36 -03:00
commit 2f34e25f06
26 changed files with 576 additions and 191 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>
@ -669,14 +672,21 @@
</menu>
<separator />
<item command="SaveFile" text="@.file_save" />
<item command="SaveFileAs" text="@.file_save_as" />
<item command="SaveFileCopyAs" text="@.file_export" group="file_save" />
<item command="SaveFileAs" text="@.file_save_as" group="file_save" />
<item command="CloseFile" text="@.file_close" />
<item command="CloseAllFiles" text="@.file_close_all" group="file_close" />
<separator />
<item command="ImportSpriteSheet" text="@.file_import_sprite_sheet" group="file_import" />
<item command="ExportSpriteSheet" text="@.file_export_sprite_sheet" group="file_export" />
<item command="RepeatLastExport" text="@.file_repeat_last_export" />
<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_1" />
<separator />
<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_1" />
</menu>
<separator id="scripts_menu_separator" />
<menu id="scripts_menu" text="@.file_scripts" group="file_scripts">
<item command="OpenScriptFolder" text="@.file_open_script_folder" />

View File

@ -508,6 +508,7 @@
<section id="save_copy" canclear="true">
<option id="filename" type="std::string" />
<option id="resize_scale" type="double" default="1" />
<option id="area" type="std::string" />
<option id="layer" type="std::string" />
<option id="frame_tag" type="std::string" />
<option id="ani_dir" type="doc::AniDir" default="doc::AniDir::FORWARD" />
@ -539,6 +540,7 @@
<option id="frame_tag" type="std::string" />
<option id="split_layers" type="bool" default="false" />
<option id="split_tags" type="bool" default="false" />
<option id="split_grid" type="bool" default="false" />
<option id="list_layers" type="bool" default="true" />
<option id="list_frame_tags" type="bool" default="true" />
<option id="list_slices" type="bool" default="true" />

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
@ -630,6 +631,7 @@ sensors_tweaks = Sensor Threshold
title = Export File
output_file = Output File:
resize = Resize:
area = Area:
layers = Layers:
frames = Frames:
anidir = Animation Direction:
@ -705,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
@ -885,12 +888,15 @@ file_no_recent_file = No Recent File
file_clear_recent_files = &Clear Recent Files
file_save = &Save
file_save_as = Save &As...
file_export = Expor&t...
file_close = &Close
file_close_all = Close All
file_import_sprite_sheet = &Import Sprite Sheet
file_export_sprite_sheet = &Export Sprite Sheet
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
file_scripts = Scri&pts
file_open_script_folder = &Open Scripts Folder
file_rescan_script_folder = &Rescan Scripts Folder

View File

@ -9,21 +9,24 @@
<button id="output_filename_browse" text="..." style="mini_button" />
<label id="resize_label" text="@.resize" />
<combobox id="resize" editable="true" suffix="%"
cell_align="horizontal" cell_hspan="2">
<listitem text="25" />
<listitem text="50" />
<listitem text="100" />
<listitem text="200" />
<listitem text="300" />
<listitem text="400" />
<listitem text="500" />
<listitem text="600" />
<listitem text="700" />
<listitem text="800" />
<listitem text="900" />
<listitem text="1000" />
</combobox>
<hbox cell_hspan="2" cell_align="horizontal">
<combobox id="resize" editable="true" suffix="%" expansive="true">
<listitem text="25" />
<listitem text="50" />
<listitem text="100" />
<listitem text="200" />
<listitem text="300" />
<listitem text="400" />
<listitem text="500" />
<listitem text="600" />
<listitem text="700" />
<listitem text="800" />
<listitem text="900" />
<listitem text="1000" />
</combobox>
<label id="area_label" text="@.area" />
<combobox id="area" text="" cell_align="horizontal" cell_hspan="2" expansive="true" />
</hbox>
<label id="layers_label" text="@.layers" />
<combobox id="layers" text="" cell_align="horizontal" cell_hspan="2" />

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
@ -49,6 +49,7 @@ AppOptions::AppOptions(int argc, const char* argv[])
, m_splitLayers(m_po.add("split-layers").description("Save each visible layer of sprites\nas separated images in the sheet\n"))
, m_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_splitGrid(m_po.add("split-grid").description("Save each grid tile as a separated file"))
, m_layer(m_po.add("layer").alias("import-layer").requiresValue("<name>").description("Include just the given layer in the sheet\nor save as operation"))
, m_allLayers(m_po.add("all-layers").description("Make all layers visible\nBy default hidden layers will be ignored"))
, m_ignoreLayer(m_po.add("ignore-layer").requiresValue("<name>").description("Exclude the given layer in the sheet\nor save as operation"))
@ -62,6 +63,7 @@ AppOptions::AppOptions(int argc, const char* argv[])
, m_trim(m_po.add("trim").description("Trim whole sprite for --save-as\nor individual frames for --sheet"))
, m_trimSprite(m_po.add("trim-sprite").description("Trim the whole sprite (for --save-as and --sheet)"))
, m_trimByGrid(m_po.add("trim-by-grid").description("Trim all images by its correspondent grid boundaries before exporting"))
, m_extrude(m_po.add("extrude").description("Extrude all images duplicating all edges one pixel"))
, 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("<name>").description("Crop the sprite to the given slice area"))
, m_filenameFormat(m_po.add("filename-format").requiresValue("<fmt>").description("Special format to generate filenames"))
@ -73,6 +75,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
@ -65,6 +65,7 @@ public:
const Option& splitLayers() const { return m_splitLayers; }
const Option& splitTags() const { return m_splitTags; }
const Option& splitSlices() const { return m_splitSlices; }
const Option& splitGrid() const { return m_splitGrid; }
const Option& layer() const { return m_layer; }
const Option& allLayers() const { return m_allLayers; }
const Option& ignoreLayer() const { return m_ignoreLayer; }
@ -78,6 +79,7 @@ public:
const Option& trim() const { return m_trim; }
const Option& trimSprite() const { return m_trimSprite; }
const Option& trimByGrid() const { return m_trimByGrid; }
const Option& extrude() const { return m_extrude; }
const Option& crop() const { return m_crop; }
const Option& slice() const { return m_slice; }
const Option& filenameFormat() const { return m_filenameFormat; }
@ -89,6 +91,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
@ -131,6 +134,7 @@ private:
Option& m_splitLayers;
Option& m_splitTags;
Option& m_splitSlices;
Option& m_splitGrid;
Option& m_layer;
Option& m_allLayers;
Option& m_ignoreLayer;
@ -144,6 +148,7 @@ private:
Option& m_trim;
Option& m_trimSprite;
Option& m_trimByGrid;
Option& m_extrude;
Option& m_crop;
Option& m_slice;
Option& m_filenameFormat;
@ -155,6 +160,7 @@ private:
Option& m_listTags;
Option& m_listSlices;
Option& m_oneFrame;
Option& m_exportTileset;
Option& m_verbose;
Option& m_debug;

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
@ -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);
@ -47,6 +28,7 @@ FileOpROI CliOpenFile::roi() const
selFrames.insert(fromFrame, toFrame);
return FileOpROI(document,
gfx::Rect(),
slice,
tag,
selFrames,

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,30 @@ 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 splitGrid = 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

@ -283,6 +283,10 @@ int CliProcessor::process(Context* ctx)
else if (opt == &m_options.splitSlices()) {
cof.splitSlices = true;
}
// --split-grid
else if (opt == &m_options.splitGrid()) {
cof.splitGrid = true;
}
// --layer <layer-name>
else if (opt == &m_options.layer()) {
cof.includeLayers.push_back(value.value());
@ -357,6 +361,11 @@ int CliProcessor::process(Context* ctx)
m_exporter->setTrimByGrid(true);
}
}
// --extrude
else if (opt == &m_options.extrude()) {
if (m_exporter)
m_exporter->setExtrude(true);
}
// --crop x,y,width,height
else if (opt == &m_options.crop()) {
std::vector<std::string> parts;
@ -578,6 +587,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 +690,20 @@ 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.splitGrid,
(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,12 @@ enum Section {
kSectionOutput,
};
enum Source {
kSource_Sprite,
kSource_SpriteGrid,
kSource_Tilesets,
};
enum ConstraintType {
kConstraintType_None,
kConstraintType_Cols,
@ -194,9 +172,11 @@ Doc* generate_sprite_sheet_from_params(
const bool mergeDuplicates = params.mergeDuplicates();
const bool splitLayers = params.splitLayers();
const bool splitTags = params.splitTags();
const bool splitGrid = params.splitGrid();
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 +206,20 @@ Doc* generate_sprite_sheet_from_params(
}
exporter.reset();
exporter.addDocumentSamples(
doc, tag, splitLayers, splitTags,
!selLayers.empty() ? &selLayers: nullptr,
!selFrames.empty() ? &selFrames: nullptr);
// Use each tileset from tilemap layers as a sprite
if (fromTilesets) {
exporter.addTilesetsSamples(
doc,
!selLayers.empty() ? &selLayers: nullptr);
}
// Use the whole canvas as a sprite
else {
exporter.addDocumentSamples(
doc, tag, splitLayers, splitTags, splitGrid,
!selLayers.empty() ? &selLayers: nullptr,
!selFrames.empty() ? &selFrames: nullptr);
}
if (saveData) {
if (!filename.empty())
@ -373,6 +363,18 @@ public:
break;
}
static_assert(kSource_Sprite == 0 &&
kSource_SpriteGrid == 1 &&
kSource_Tilesets == 2,
"Source enum has changed");
source()->addItem(new ListItem("Sprite"));
source()->addItem(new ListItem("Sprite Grid"));
source()->addItem(new ListItem("Tilesets"));
if (params.splitGrid())
source()->setSelectedItemIndex(int(kSource_SpriteGrid));
else if (params.fromTilesets())
source()->setSelectedItemIndex(int(kSource_Tilesets));
fill_layers_combobox(
m_sprite, layers(), params.layer());
@ -446,6 +448,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 +539,8 @@ public:
params.listLayers (listLayersValue());
params.listTags (listTagsValue());
params.listSlices (listSlicesValue());
params.splitGrid (source()->getSelectedItemIndex() == int(kSource_SpriteGrid));
params.fromTilesets (source()->getSelectedItemIndex() == int(kSource_Tilesets));
}
private:
@ -727,6 +732,10 @@ private:
return splitTags()->isSelected();
}
bool splitGridValue() const {
return (source()->getSelectedItemIndex() == int(kSource_SpriteGrid));
}
bool listLayersValue() const {
return listLayers()->isSelected();
}
@ -1169,16 +1178,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)
{
}
@ -1236,6 +1237,7 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
if (!params.tag.isSet()) params.tag( defPref.spriteSheet.frameTag());
if (!params.splitLayers.isSet()) params.splitLayers( defPref.spriteSheet.splitLayers());
if (!params.splitTags.isSet()) params.splitTags( defPref.spriteSheet.splitTags());
if (!params.splitGrid.isSet()) params.splitGrid( defPref.spriteSheet.splitGrid());
if (!params.listLayers.isSet()) params.listLayers( defPref.spriteSheet.listLayers());
if (!params.listTags.isSet()) params.listTags( defPref.spriteSheet.listFrameTags());
if (!params.listSlices.isSet()) params.listSlices( defPref.spriteSheet.listSlices());
@ -1282,6 +1284,7 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
docPref.spriteSheet.frameTag (params.tag());
docPref.spriteSheet.splitLayers (params.splitLayers());
docPref.spriteSheet.splitTags (params.splitTags());
docPref.spriteSheet.splitGrid (params.splitGrid());
docPref.spriteSheet.listLayers (params.listLayers());
docPref.spriteSheet.listFrameTags (params.listTags());
docPref.spriteSheet.listSlices (params.listSlices());

View File

@ -0,0 +1,63 @@
// 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> splitGrid { this, false, "splitGrid" };
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

@ -37,6 +37,7 @@
#include "base/fs.h"
#include "base/scoped_value.h"
#include "base/thread.h"
#include "doc/mask.h"
#include "doc/sprite.h"
#include "doc/tag.h"
#include "fmt/format.h"
@ -204,7 +205,12 @@ void SaveFileBaseCommand::saveDocumentInBackground(
}
}
FileOpROI roi(document, params().slice(), params().tag(),
gfx::Rect bounds;
if (params().bounds.isSet())
bounds = params().bounds();
FileOpROI roi(document, bounds,
params().slice(), params().tag(),
m_selFrames, m_adjustFramesByTag);
std::unique_ptr<FileOp> fop(
@ -346,6 +352,7 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
std::string frames = kAllFrames;
bool applyPixelRatio = false;
double scale = params().scale();
gfx::Rect bounds = params().bounds();
doc::AniDir aniDirValue = params().aniDir();
bool isForTwitter = false;
@ -382,6 +389,13 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
if (params().scale.isSet()) win.setResizeScale(scale);
if (params().aniDir.isSet()) win.setAniDir(aniDirValue);
if (params().slice.isSet()) win.setArea(params().slice());
else if (params().bounds.isSet() &&
doc->isMaskVisible() &&
doc->mask()->bounds() == params().bounds()) {
win.setArea(kSelectedCanvas);
}
win.remapWindow();
load_window_pos(&win, "ExportFile");
again:;
@ -410,6 +424,9 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
layers = win.layersValue();
frames = win.framesValue();
scale = win.resizeValue();
params().slice(win.areaValue()); // Set slice
if (win.areaValue() == kSelectedCanvas && doc->isMaskVisible())
bounds = doc->mask()->bounds();
applyPixelRatio = win.applyPixelRatio();
aniDirValue = win.aniDirValue();
isForTwitter = win.isForTwitter();
@ -480,8 +497,10 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
m_adjustFramesByTag = false;
}
// Set ani dir
// Set other parameters
params().aniDir(aniDirValue);
if (!bounds.isEmpty())
params().bounds(bounds);
// TODO This should be set as options for the specific encoder
GifEncoderDurationFix fixGif(isForTwitter);

View File

@ -14,6 +14,7 @@
#include "doc/anidir.h"
#include "doc/selected_frames.h"
#include "gfx/point.h"
#include "gfx/rect.h"
#include <string>
@ -31,6 +32,7 @@ namespace app {
Param<doc::frame_t> toFrame { this, 0, { "toFrame", "to-frame" } };
Param<bool> ignoreEmpty { this, false, "ignoreEmpty" };
Param<double> scale { this, 1.0, "scale" };
Param<gfx::Rect> bounds { this, gfx::Rect(), "bounds" };
};
class SaveFileBaseCommand : public CommandWithNewParams<SaveFileParams> {

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

@ -96,29 +96,25 @@ typedef std::shared_ptr<gfx::Rect> SharedRectPtr;
DocExporter::Item::Item(Doc* doc,
const doc::Tag* tag,
const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames)
const doc::SelectedFrames* selFrames,
const bool splitGrid)
: doc(doc)
, tag(tag)
, selLayers(selLayers ? new doc::SelectedLayers(*selLayers): nullptr)
, selFrames(selFrames ? new doc::SelectedFrames(*selFrames): nullptr)
, selLayers(selLayers ? std::make_unique<doc::SelectedLayers>(*selLayers): nullptr)
, selFrames(selFrames ? std::make_unique<doc::SelectedFrames>(*selFrames): nullptr)
, splitGrid(splitGrid)
{
}
DocExporter::Item::Item(Item&& other)
: doc(other.doc)
, tag(other.tag)
, selLayers(other.selLayers)
, selFrames(other.selFrames)
DocExporter::Item::Item(Doc* doc,
const doc::ImageRef& image)
: doc(doc)
, image(image)
{
other.selLayers = nullptr;
other.selFrames = nullptr;
}
DocExporter::Item::~Item()
{
delete selLayers;
delete selFrames;
}
DocExporter::Item::Item(Item&& other) = default;
DocExporter::Item::~Item() = default;
int DocExporter::Item::frames() const
{
@ -142,6 +138,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());
}
@ -150,7 +148,11 @@ doc::SelectedFrames DocExporter::Item::getSelectedFrames() const
class DocExporter::Sample {
public:
Sample(Doc* document, Sprite* sprite, SelectedLayers* selLayers,
Sample(const gfx::Size& size,
Doc* document,
Sprite* sprite,
const ImageRef& image,
SelectedLayers* selLayers,
frame_t frame,
const Tag* tag,
const std::string& filename,
@ -158,6 +160,7 @@ public:
const bool extrude) :
m_document(document),
m_sprite(sprite),
m_image(image),
m_selLayers(selLayers),
m_frame(frame),
m_tag(tag),
@ -166,9 +169,9 @@ 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(size),
m_trimmedBounds(size),
m_inTextureBounds(std::make_shared<gfx::Rect>(size)) {
}
Doc* document() const { return m_document; }
@ -234,6 +237,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,
@ -279,19 +287,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;
@ -360,7 +381,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 ||
@ -492,7 +513,7 @@ public:
++i;
}
DX_TRACE("-> SimpleLayoutSamples", width, height);
DX_TRACE("DX: -> SimpleLayoutSamples", width, height);
}
private:
@ -688,7 +709,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)
@ -713,11 +734,19 @@ void DocExporter::addDocument(
Doc* doc,
const doc::Tag* tag,
const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames)
const doc::SelectedFrames* selFrames,
const bool splitGrid)
{
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, splitGrid));
}
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(
@ -725,10 +754,11 @@ int DocExporter::addDocumentSamples(
const doc::Tag* thisTag,
const bool splitLayers,
const bool splitTags,
const bool splitGrid,
const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames)
{
DX_TRACE("DocExporter::addDocumentSamples");
DX_TRACE("DX: addDocumentSamples");
std::vector<const Tag*> tags;
@ -793,7 +823,7 @@ int DocExporter::addDocumentSamples(
SelectedLayers oneLayer;
oneLayer.insert(layer);
addDocument(doc, tag, &oneLayer, thisSelFrames);
addDocument(doc, tag, &oneLayer, thisSelFrames, splitGrid);
++items;
}
}
@ -804,19 +834,49 @@ int DocExporter::addDocumentSamples(
SelectedLayers oneLayer;
oneLayer.insert(layer);
addDocument(doc, tag, &oneLayer, thisSelFrames);
addDocument(doc, tag, &oneLayer, thisSelFrames, splitGrid);
++items;
}
}
}
else {
addDocument(doc, tag, selLayers, thisSelFrames);
addDocument(doc, tag, selLayers, thisSelFrames, splitGrid);
++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,
base::task_token& token)
{
@ -847,24 +907,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;
}
}
}
@ -891,8 +961,12 @@ void DocExporter::captureSamples(Samples& samples,
std::string filename = filename_formatter(format, fnInfo);
Sample sample(
doc, sprite, item.selLayers, frame, innerTag,
filename, m_innerPadding, m_extrude);
(item.image ? item.image->size():
item.splitGrid ? sprite->gridBounds().size():
sprite->size()),
doc, sprite, item.image, item.selLayers.get(),
frame, innerTag, filename,
m_innerPadding, m_extrude);
Cel* cel = nullptr;
Cel* link = nullptr;
bool done = false;
@ -905,7 +979,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;
@ -928,7 +1003,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;
@ -994,7 +1070,23 @@ void DocExporter::captureSamples(Samples& samples,
if (!alreadyTrimmed && m_trimSprite)
sample.setTrimmedBounds(spriteBounds);
samples.addSample(sample);
if (item.splitGrid) {
const gfx::Rect& gridBounds = sprite->gridBounds();
gfx::Point initPos(0, 0), pos;
initPos = pos = snap_to_grid(gridBounds, initPos, PreferSnapTo::BoxOrigin);
for (; pos.y+gridBounds.h <= spriteBounds.h; pos.y+=gridBounds.h) {
for (pos.x=initPos.x; pos.x+gridBounds.w <= spriteBounds.w; pos.x+=gridBounds.w) {
const gfx::Rect cellBounds(pos, gridBounds.size());
sample.setTrimmedBounds(cellBounds);
sample.setSharedBounds(std::make_shared<gfx::Rect>(sample.inTextureBounds()));
samples.addSample(sample);
}
}
}
else {
samples.addSample(sample);
}
DX_TRACE("DX: - Sample:",
sample.document()->filename(),
@ -1319,6 +1411,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();
@ -1350,6 +1445,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();
@ -1445,6 +1543,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"
@ -21,6 +22,7 @@
#include "gfx/rect.h"
#include <iosfwd>
#include <memory>
#include <string>
#include <utility>
#include <vector>
@ -75,20 +77,23 @@ namespace app {
void setListLayers(bool value) { m_listLayers = value; }
void setListSlices(bool value) { m_listSlices = value; }
void addDocument(
void addImage(
Doc* doc,
const doc::Tag* tag,
const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames);
const doc::ImageRef& image);
int addDocumentSamples(
Doc* doc,
const doc::Tag* tag,
const bool splitLayers,
const bool splitTags,
const bool splitGrid,
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();
@ -99,6 +104,12 @@ namespace app {
class SimpleLayoutSamples;
class BestFitLayoutSamples;
void addDocument(
Doc* doc,
const doc::Tag* tag,
const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames,
const bool splitGrid);
void captureSamples(Samples& samples,
base::task_token& token);
void layoutSamples(Samples& samples,
@ -116,15 +127,20 @@ namespace app {
class Item {
public:
Doc* doc;
const doc::Tag* tag;
doc::SelectedLayers* selLayers;
doc::SelectedFrames* selFrames;
Doc* doc = nullptr;
const doc::Tag* tag = nullptr;
std::unique_ptr<doc::SelectedLayers> selLayers;
std::unique_ptr<doc::SelectedFrames> selFrames;
bool splitGrid = false;
doc::ImageRef image;
Item(Doc* doc,
const doc::Tag* tag,
const doc::SelectedLayers* selLayers,
const doc::SelectedFrames* selFrames);
const doc::SelectedFrames* selFrames,
const bool splitGrid);
Item(Doc* doc,
const doc::ImageRef& image);
Item(Item&& other);
~Item();
@ -134,6 +150,8 @@ namespace app {
int frames() const;
doc::SelectedFrames getSelectedFrames() const;
bool isOneImageOnly() const { return image != nullptr; }
};
typedef std::vector<Item> Items;

View File

@ -65,9 +65,9 @@ public:
ASSERT(m_doc && m_sprite);
}
void setSliceBounds(const gfx::Rect& sliceBounds) {
m_spec.setWidth(sliceBounds.w * m_scale.x);
m_spec.setHeight(sliceBounds.h * m_scale.y);
void setSpecSize(const gfx::Size& size) {
m_spec.setWidth(size.w * m_scale.x);
m_spec.setHeight(size.h * m_scale.y);
}
void setUnscaledImage(const doc::frame_t frame,
@ -233,7 +233,7 @@ int save_document(Context* context, Doc* document)
std::unique_ptr<FileOp> fop(
FileOp::createSaveDocumentOperation(
context,
FileOpROI(document, "", "", SelectedFrames(), false),
FileOpROI(document, gfx::Rect(), "", "", SelectedFrames(), false),
document->filename(), "",
false));
if (!fop)
@ -269,11 +269,13 @@ FileOpROI::FileOpROI()
}
FileOpROI::FileOpROI(const Doc* doc,
const gfx::Rect& bounds,
const std::string& sliceName,
const std::string& tagName,
const doc::SelectedFrames& selFrames,
const bool adjustByTag)
: m_document(doc)
, m_bounds(bounds)
, m_slice(nullptr)
, m_tag(nullptr)
, m_selFrames(selFrames)
@ -916,23 +918,35 @@ void FileOp::operate(IFileOpProgress* progress)
frame_t outputFrame = 0;
for (frame_t frame : m_roi.selectedFrames()) {
// Draw the "frame" in "m_seq.image"
gfx::Rect bounds;
// Export bounds of specific slice
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
bounds = key->bounds();
}
// Export specific bounds
else if (!m_roi.bounds().isEmpty()) {
bounds = m_roi.bounds();
}
// Draw the "frame" in "m_seq.image" with the given bounds
// (bounds can be the selection bounds or a slice key bounds)
if (!bounds.isEmpty()) {
if (m_abstractImage)
m_abstractImage->setSliceBounds(key->bounds());
m_abstractImage->setSpecSize(bounds.size());
m_seq.image.reset(
Image::create(sprite->pixelFormat(),
key->bounds().w,
key->bounds().h));
bounds.w,
bounds.h));
render.renderSprite(
m_seq.image.get(), sprite, frame,
gfx::Clip(gfx::Point(0, 0), key->bounds()));
gfx::Clip(gfx::Point(0, 0), bounds));
}
else {
render.renderSprite(m_seq.image.get(), sprite, frame);

View File

@ -72,12 +72,14 @@ namespace app {
public:
FileOpROI();
FileOpROI(const Doc* doc,
const gfx::Rect& bounds,
const std::string& sliceName,
const std::string& tagName,
const doc::SelectedFrames& selFrames,
const bool adjustByTag);
const Doc* document() const { return m_document; }
const gfx::Rect& bounds() const { return m_bounds; }
doc::Slice* slice() const { return m_slice; }
doc::Tag* tag() const { return m_tag; }
doc::frame_t fromFrame() const { return m_selFrames.firstFrame(); }
@ -90,6 +92,7 @@ namespace app {
private:
const Doc* m_document;
gfx::Rect m_bounds;
doc::Slice* m_slice;
doc::Tag* m_tag;
doc::SelectedFrames m_selFrames;

View File

@ -52,6 +52,7 @@ ExportFileWindow::ExportFileWindow(const Doc* doc)
// Default export configuration
setResizeScale(m_docPref.saveCopy.resizeScale());
fill_area_combobox(m_doc->sprite(), area(), m_docPref.saveCopy.area());
fill_layers_combobox(m_doc->sprite(), layers(), m_docPref.saveCopy.layer());
fill_frames_combobox(m_doc->sprite(), frames(), m_docPref.saveCopy.frameTag());
fill_anidir_combobox(anidir(), m_docPref.saveCopy.aniDir());
@ -97,6 +98,7 @@ void ExportFileWindow::savePref()
{
m_docPref.saveCopy.filename(outputFilenameValue());
m_docPref.saveCopy.resizeScale(resizeValue());
m_docPref.saveCopy.area(areaValue());
m_docPref.saveCopy.layer(layersValue());
m_docPref.saveCopy.aniDir(aniDirValue());
m_docPref.saveCopy.frameTag(framesValue());
@ -116,6 +118,11 @@ double ExportFileWindow::resizeValue() const
return std::clamp(value, 0.001, 100000000.0);
}
std::string ExportFileWindow::areaValue() const
{
return area()->getValue();
}
std::string ExportFileWindow::layersValue() const
{
return layers()->getValue();
@ -146,6 +153,11 @@ void ExportFileWindow::setResizeScale(double scale)
resize()->setValue(fmt::format("{:.2f}", 100.0 * scale));
}
void ExportFileWindow::setArea(const std::string& areaValue)
{
area()->setValue(areaValue);
}
void ExportFileWindow::setAniDir(const doc::AniDir aniDir)
{
anidir()->setSelectedItemIndex(int(aniDir));

View File

@ -28,6 +28,7 @@ namespace app {
std::string outputFilenameValue() const;
double resizeValue() const;
std::string areaValue() const;
std::string layersValue() const;
std::string framesValue() const;
doc::AniDir aniDirValue() const;
@ -36,6 +37,7 @@ namespace app {
void setOutputFilename(const std::string& pathAndFilename);
void setResizeScale(const double scale);
void setArea(const std::string& area);
void setAniDir(const doc::AniDir aniDir);
obs::signal<std::string()> SelectOutputFile;

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-2018 David Capello
//
// This program is distributed under the terms of
@ -17,17 +17,27 @@
#include "doc/layer.h"
#include "doc/selected_frames.h"
#include "doc/selected_layers.h"
#include "doc/slice.h"
#include "doc/sprite.h"
#include "doc/tag.h"
#include "ui/combobox.h"
namespace app {
const char* kWholeCanvas = "";
const char* kAllLayers = "";
const char* kAllFrames = "";
const char* kSelectedCanvas = "**selected-canvas**";
const char* kSelectedLayers = "**selected-layers**";
const char* kSelectedFrames = "**selected-frames**";
SliceListItem::SliceListItem(doc::Slice* slice)
: ListItem("Slice: " + slice->name())
, m_slice(slice)
{
setValue(m_slice->name());
}
LayerListItem::LayerListItem(doc::Layer* layer)
: ListItem(buildName(layer))
, m_layer(layer)
@ -57,6 +67,26 @@ FrameListItem::FrameListItem(doc::Tag* tag)
setValue(m_tag->name());
}
void fill_area_combobox(const doc::Sprite* sprite, ui::ComboBox* area, const std::string& defArea)
{
int i = area->addItem("Canvas");
dynamic_cast<ui::ListItem*>(area->getItem(i))->setValue(kWholeCanvas);
i = area->addItem("Selection");
dynamic_cast<ui::ListItem*>(area->getItem(i))->setValue(kSelectedCanvas);
if (defArea == kSelectedCanvas)
area->setSelectedItemIndex(i);
for (auto slice : sprite->slices()) {
if (slice->name().empty())
continue;
i = area->addItem(new SliceListItem(slice));
if (defArea == slice->name())
area->setSelectedItemIndex(i);
}
}
void fill_layers_combobox(const doc::Sprite* sprite, ui::ComboBox* layers, const std::string& defLayer)
{
int i = layers->addItem("Visible layers");

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-2018 David Capello
//
// This program is distributed under the terms of
@ -18,6 +18,7 @@ namespace doc {
class Layer;
class SelectedFrames;
class SelectedLayers;
class Slice;
class Sprite;
class Tag;
}
@ -30,11 +31,21 @@ namespace app {
class RestoreVisibleLayers;
class Site;
extern const char* kWholeCanvas;
extern const char* kAllLayers;
extern const char* kAllFrames;
extern const char* kSelectedCanvas;
extern const char* kSelectedLayers;
extern const char* kSelectedFrames;
class SliceListItem : public ui::ListItem {
public:
SliceListItem(doc::Slice* slice);
doc::Slice* slice() const { return m_slice; }
private:
doc::Slice* m_slice;
};
class LayerListItem : public ui::ListItem {
public:
LayerListItem(doc::Layer* layer);
@ -52,6 +63,7 @@ namespace app {
doc::Tag* m_tag;
};
void fill_area_combobox(const doc::Sprite* sprite, ui::ComboBox* area, const std::string& defArea);
void fill_layers_combobox(const doc::Sprite* sprite, ui::ComboBox* layers, const std::string& defLayer);
void fill_frames_combobox(const doc::Sprite* sprite, ui::ComboBox* frames, const std::string& defFrame);
void fill_anidir_combobox(ui::ComboBox* anidir, doc::AniDir defAnidir);