mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-29 19:20:09 +00:00
Simplify Export Sprite Sheet with preview + changes to the UI
These changes include an option to split layers and tags by rows (fix #1118)
This commit is contained in:
parent
7fd1c7e0e7
commit
536a4c5d3a
@ -309,6 +309,7 @@
|
||||
<section id="sprite_sheet">
|
||||
<option id="show_overwrite_files_alert" type="bool" default="true" />
|
||||
<option id="default_extension" type="std::string" default=""png"" />
|
||||
<option id="preview" type="bool" default="true" />
|
||||
</section>
|
||||
<section id="gif">
|
||||
<option id="show_alert" type="bool" default="true" />
|
||||
@ -447,13 +448,14 @@
|
||||
<option id="rows" type="int" default="0" />
|
||||
<option id="width" type="int" default="0" />
|
||||
<option id="height" type="int" default="0" />
|
||||
<option id="best_fit" type="bool" default="false" />
|
||||
<option id="texture_filename" type="std::string" />
|
||||
<option id="data_filename" type="std::string" />
|
||||
<option id="data_format" type="SpriteSheetDataFormat" default="SpriteSheetDataFormat::Default" />
|
||||
<option id="filename_format" type="std::string" />
|
||||
<option id="border_padding" type="int" default="0" />
|
||||
<option id="shape_padding" type="int" default="0" />
|
||||
<option id="inner_padding" type="int" default="0" />
|
||||
<option id="trim_sprite" type="bool" default="false" />
|
||||
<option id="trim" type="bool" default="false" />
|
||||
<option id="trim_by_grid" type="bool" default="false" />
|
||||
<option id="extrude" type="bool" default="false" />
|
||||
@ -461,6 +463,7 @@
|
||||
<option id="layer" type="std::string" />
|
||||
<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="list_layers" type="bool" default="true" />
|
||||
<option id="list_frame_tags" type="bool" default="true" />
|
||||
<option id="list_slices" type="bool" default="true" />
|
||||
|
@ -534,25 +534,56 @@ cancel = &Cancel
|
||||
|
||||
[export_sprite_sheet]
|
||||
title = Export Sprite Sheet
|
||||
format = Format:
|
||||
sheet_type = Sheet Type:
|
||||
columns = # of Columns:
|
||||
rows = # of Rows:
|
||||
padding = Padding
|
||||
border = Border:
|
||||
shape = Shape:
|
||||
inner = Inner:
|
||||
trim = Trim
|
||||
trim_by_grid = by Grid
|
||||
extrude = Extrude
|
||||
width = Width:
|
||||
height = Height:
|
||||
best_fit = Best fit for texture
|
||||
layers = Layers:
|
||||
split_layers = Split
|
||||
split_layers_tooltip = <<<END
|
||||
Generates one sprite for each layer (and for each frame).
|
||||
sheet_type_tooltip = <<<END
|
||||
Indicates a specific way to layout sprites in the sprite sheet:
|
||||
* Horizontal: Each frame side by side.
|
||||
* Vertical: Each frame one below the other.
|
||||
* By Rows: Create one row for each layer or tag.
|
||||
* By Columns: Create one column for each layer or tag.
|
||||
* Packed: Try to fit all frames in the best possible way.
|
||||
END
|
||||
type_horz = Horizontal Strip
|
||||
type_vert = Vertical Strip
|
||||
type_rows = By Rows
|
||||
type_cols = By Columns
|
||||
type_pack = Packed
|
||||
constraints = Constraints
|
||||
constraints_tooltip = <<<END
|
||||
Special constraints for the sprite sheet.
|
||||
E.g. Fixed number of rows/columns or fixed
|
||||
number of pixels (width/height).
|
||||
END
|
||||
constraint_fixed_none = None
|
||||
constraint_fixed_cols = Fixed # of Columns
|
||||
constraint_fixed_rows = Fixed # of Rows
|
||||
constraint_fixed_width = Fixed Width
|
||||
constraint_fixed_height = Fixed Height
|
||||
constraint_fixed_size = Fixed Size
|
||||
padding = Padding
|
||||
padding_tooltip = Extra space to add between sprites
|
||||
border = Border:
|
||||
border_tooltip = Space between each frame and the edge of the sprite sheet
|
||||
shape = Spacing:
|
||||
shape_tooltip = Space between each frame in the sprite sheet
|
||||
inner = Inner:
|
||||
inner_tooltip = Extra space inside frame edges
|
||||
trim_sprite = Trim Sprite
|
||||
trim_sprite_tooltip = Trims the whole sprite before its frames are included in the sprite sheet
|
||||
trim = Trim Cels
|
||||
trim_tooltip = Trims each frame separately
|
||||
trim_by_grid = By Grid
|
||||
trim_by_grid_tooltip = Trims by grid boundaries instead of pixel by pixel
|
||||
extrude = Extrude
|
||||
extrude_tooltip = Adds a border to each frame duplicating the pixels of its edges
|
||||
layers = Layers:
|
||||
split_layers = Split Layers
|
||||
split_layers_tooltip = Generates one sprite for each layer
|
||||
split_tags = Split Tags
|
||||
split_tags_tooltip = Generates one sprite for each tag
|
||||
frames = Frames:
|
||||
output = Output:
|
||||
output_file = Output File
|
||||
json_data = JSON Data
|
||||
json_data_hash = Hash
|
||||
@ -561,9 +592,18 @@ meta = Meta:
|
||||
meta_layers = Layers
|
||||
meta_tags = Tags
|
||||
meta_slices = Slices
|
||||
open_sprite_sheet = Open generated sprite sheet
|
||||
data_filename_format = Item Filename:
|
||||
data_filename_format_tooltip = <<<END
|
||||
Each frame in the JSON data will contain a filename
|
||||
(string that identifies them), this field describes
|
||||
how to build each frame. You can use special marks
|
||||
like {layer}, {frame}, {tag}, {tagframe}, etc.
|
||||
END
|
||||
preview = Preview
|
||||
open_sprite_sheet = Open Generated Sprite Sheet
|
||||
export = &Export
|
||||
cancel = &Cancel
|
||||
generating = Generating...
|
||||
|
||||
[file_selector]
|
||||
go_back_button_tooltip = Go back one folder
|
||||
|
@ -3,51 +3,61 @@
|
||||
<!-- Copyright (C) 2001-2018 David Capello -->
|
||||
<gui>
|
||||
<window id="export_sprite_sheet" text="@.title">
|
||||
<grid columns="4">
|
||||
<grid columns="4" expansive="true">
|
||||
<separator horizontal="true" text="@.format" cell_hspan="4" />
|
||||
|
||||
<label text="@.sheet_type" />
|
||||
<combobox id="sheet_type" cell_hspan="3" />
|
||||
<combobox id="sheet_type" expansive="true" cell_hspan="2" cell_align="horizontal"
|
||||
tooltip="@.sheet_type_tooltip" />
|
||||
<check id="show_constraints" text="@.constraints"
|
||||
tooltip="@.constraints_tooltip" />
|
||||
|
||||
<label id="columns_label" text="@.columns" />
|
||||
<expr id="columns" text="" />
|
||||
<boxfiller cell_hspan="2" />
|
||||
|
||||
<label id="rows_label" text="@.rows" />
|
||||
<expr id="rows" text="" />
|
||||
<boxfiller cell_hspan="2" />
|
||||
|
||||
<hbox />
|
||||
<vbox>
|
||||
<check id="padding_enabled" text="@.padding" />
|
||||
<check id="trim_enabled" text="@.trim" />
|
||||
<hbox id="trim_container">
|
||||
<boxfiller />
|
||||
<check id="grid_trim_enabled" text="@.trim_by_grid" />
|
||||
</hbox>
|
||||
<check id="extrude_enabled" text="@.extrude" />
|
||||
</vbox>
|
||||
<grid columns="2" id="padding_container" cell_hspan="2">
|
||||
<label text="@.border" />
|
||||
<expr id="border_padding" text="0" />
|
||||
<label text="@.shape" />
|
||||
<expr id="shape_padding" text="0" />
|
||||
<label text="@.inner" />
|
||||
<expr id="inner_padding" text="0" />
|
||||
</grid>
|
||||
|
||||
<label id="fit_width_label" text="@.width" />
|
||||
<combobox id="fit_width" text="" maxsize="5" editable="true" />
|
||||
<label id="fit_height_label" text="@.height" />
|
||||
<combobox id="fit_height" text="" maxsize="5" editable="true" />
|
||||
|
||||
<hbox id="best_fit_filler" />
|
||||
<check cell_hspan="3" id="best_fit" text="@.best_fit" />
|
||||
<label id="constraints_label" text="@.constraints" />
|
||||
<hbox id="constraints_placeholder" cell_hspan="3">
|
||||
<combobox id="constraint_type" />
|
||||
<expr id="width_constraint" />
|
||||
<expr id="height_constraint" />
|
||||
</hbox>
|
||||
|
||||
<label text="@.layers" />
|
||||
<combobox id="layers" text="" cell_hspan="2" />
|
||||
<check id="split_layers" text="@.split_layers" tooltip="@.split_layers_tooltip" />
|
||||
|
||||
<label text="@.frames" />
|
||||
<combobox id="frames" text="" cell_hspan="3" />
|
||||
<combobox id="frames" text="" cell_hspan="2" />
|
||||
<check id="split_tags" text="@.split_tags" tooltip="@.split_tags_tooltip" />
|
||||
|
||||
<hbox />
|
||||
<hbox cell_hspan="3">
|
||||
<vbox>
|
||||
<check id="trim_sprite_enabled" text="@.trim_sprite" tooltip="@.trim_sprite_tooltip" />
|
||||
<check id="trim_enabled" text="@.trim" tooltip="@.trim_tooltip" />
|
||||
<vbox id="trim_container" cell_hspan="2">
|
||||
<check id="grid_trim_enabled"
|
||||
text="@.trim_by_grid"
|
||||
tooltip="@.trim_by_grid_tooltip" />
|
||||
</vbox>
|
||||
</vbox>
|
||||
<separator vertical="true" />
|
||||
<vbox>
|
||||
<check id="padding_enabled" text="@.padding" tooltip="@.padding_tooltip" />
|
||||
<check id="extrude_enabled"
|
||||
text="@.extrude"
|
||||
tooltip="@.extrude_tooltip" />
|
||||
</vbox>
|
||||
<vbox>
|
||||
<grid columns="2" id="padding_container">
|
||||
<label text="@.border" />
|
||||
<expr id="border_padding" text="0" tooltip="@.border_tooltip" />
|
||||
<label text="@.shape" />
|
||||
<expr id="shape_padding" text="0" tooltip="@.shape_tooltip" />
|
||||
<label text="@.inner" />
|
||||
<expr id="inner_padding" text="0" tooltip="@.inner_tooltip" />
|
||||
</grid>
|
||||
</vbox>
|
||||
</hbox>
|
||||
|
||||
<separator horizontal="true" text="@.output" cell_hspan="4" cell_align="horizontal" />
|
||||
|
||||
<check id="image_enabled" text="@.output_file" />
|
||||
<button id="image_filename" cell_hspan="3" />
|
||||
@ -56,7 +66,7 @@
|
||||
<button id="data_filename" cell_hspan="3" />
|
||||
|
||||
<hbox />
|
||||
<hbox id="data_meta" cell_hspan="3">
|
||||
<hbox id="data_meta" cell_hspan="3" cell_align="horizontal">
|
||||
<combobox id="data_format">
|
||||
<listitem text="@.json_data_hash" value="0" />
|
||||
<listitem text="@.json_data_array" value="1" />
|
||||
@ -67,9 +77,21 @@
|
||||
<check id="list_slices" text="@.meta_slices" />
|
||||
</hbox>
|
||||
|
||||
<check id="open_generated" text="@.open_sprite_sheet" cell_hspan="4" />
|
||||
<hbox />
|
||||
<hbox id="data_filename_format_placeholder" cell_hspan="3" cell_align="horizontal">
|
||||
<label text="@.data_filename_format" />
|
||||
<entry id="data_filename_format" maxsize="1024" maxwidth="256" expansive="true"
|
||||
tooltip="@.data_filename_format_tooltip" />
|
||||
<link text="(?)" url="https://www.aseprite.org/docs/cli/#filename-format" />
|
||||
</hbox>
|
||||
|
||||
<separator horizontal="true" cell_hspan="4" />
|
||||
|
||||
<hbox cell_hspan="4">
|
||||
<hbox>
|
||||
<check id="open_generated" text="@.open_sprite_sheet" cell_hspan="3" />
|
||||
<check id="preview" text="@.preview" cell_hspan="1" />
|
||||
</hbox>
|
||||
<boxfiller />
|
||||
<hbox homogeneous="true">
|
||||
<button text="@.export" minwidth="60" id="export_button" magnet="true" />
|
||||
|
2
laf
2
laf
@ -1 +1 @@
|
||||
Subproject commit 8cacc60740b76a8012aee3e35a6681516d4f871d
|
||||
Subproject commit 5edb4d5441571eddc2b5f378fc35e6be78048541
|
@ -55,6 +55,7 @@ AppOptions::AppOptions(int argc, const char* argv[])
|
||||
, m_tag(m_po.add("tag").alias("frame-tag").requiresValue("<name>").description("Include tagged frames in the sheet"))
|
||||
, m_frameRange(m_po.add("frame-range").requiresValue("from,to").description("Only export frames in the [from,to] range"))
|
||||
, m_ignoreEmpty(m_po.add("ignore-empty").description("Do not export empty frames/cels"))
|
||||
, m_mergeDuplicates(m_po.add("merge-duplicates").description("Merge all duplicate frames into one in the sprite sheet"))
|
||||
, m_borderPadding(m_po.add("border-padding").requiresValue("<value>").description("Add padding on the texture borders"))
|
||||
, m_shapePadding(m_po.add("shape-padding").requiresValue("<value>").description("Add padding between frames"))
|
||||
, m_innerPadding(m_po.add("inner-padding").requiresValue("<value>").description("Add padding inside each frame"))
|
||||
|
@ -69,6 +69,7 @@ public:
|
||||
const Option& tag() const { return m_tag; }
|
||||
const Option& frameRange() const { return m_frameRange; }
|
||||
const Option& ignoreEmpty() const { return m_ignoreEmpty; }
|
||||
const Option& mergeDuplicates() const { return m_mergeDuplicates; }
|
||||
const Option& borderPadding() const { return m_borderPadding; }
|
||||
const Option& shapePadding() const { return m_shapePadding; }
|
||||
const Option& innerPadding() const { return m_innerPadding; }
|
||||
@ -129,6 +130,7 @@ private:
|
||||
Option& m_tag;
|
||||
Option& m_frameRange;
|
||||
Option& m_ignoreEmpty;
|
||||
Option& m_mergeDuplicates;
|
||||
Option& m_borderPadding;
|
||||
Option& m_shapePadding;
|
||||
Option& m_innerPadding;
|
||||
|
@ -305,6 +305,11 @@ void CliProcessor::process(Context* ctx)
|
||||
if (m_exporter)
|
||||
m_exporter->setIgnoreEmptyCels(true);
|
||||
}
|
||||
// --merge-duplicates
|
||||
else if (opt == &m_options.mergeDuplicates()) {
|
||||
if (m_exporter)
|
||||
m_exporter->setMergeDuplicates(true);
|
||||
}
|
||||
// --border-padding
|
||||
else if (opt == &m_options.borderPadding()) {
|
||||
if (m_exporter)
|
||||
@ -625,37 +630,16 @@ bool CliProcessor::openFile(Context* ctx, CliOpenFile& cof)
|
||||
}
|
||||
}
|
||||
|
||||
if (cof.hasLayersFilter()) {
|
||||
SelectedLayers filteredLayers;
|
||||
SelectedLayers filteredLayers;
|
||||
if (cof.hasLayersFilter())
|
||||
filterLayers(doc->sprite(), cof, filteredLayers);
|
||||
|
||||
if (cof.splitLayers) {
|
||||
for (Layer* layer : filteredLayers.toAllLayersList()) {
|
||||
SelectedLayers oneLayer;
|
||||
oneLayer.insert(layer);
|
||||
|
||||
m_exporter->addDocument(doc, tag, &oneLayer,
|
||||
(!selFrames.empty() ? &selFrames: nullptr));
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_exporter->addDocument(doc, tag, &filteredLayers,
|
||||
(!selFrames.empty() ? &selFrames: nullptr));
|
||||
}
|
||||
}
|
||||
else if (cof.splitLayers) {
|
||||
for (auto layer : doc->sprite()->allVisibleLayers()) {
|
||||
SelectedLayers oneLayer;
|
||||
oneLayer.insert(layer);
|
||||
|
||||
m_exporter->addDocument(doc, tag, &oneLayer,
|
||||
(!selFrames.empty() ? &selFrames: nullptr));
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_exporter->addDocument(doc, tag, nullptr,
|
||||
(!selFrames.empty() ? &selFrames: nullptr));
|
||||
}
|
||||
m_exporter->addDocumentSamples(
|
||||
doc, tag,
|
||||
cof.splitLayers,
|
||||
cof.splitTags,
|
||||
(cof.hasLayersFilter() ? &filteredLayers: nullptr),
|
||||
(!selFrames.empty() ? &selFrames: nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,9 @@ void DefaultCliDelegate::exportFiles(Context* ctx, DocExporter& exporter)
|
||||
{
|
||||
LOG("APP: Exporting sheet...\n");
|
||||
|
||||
std::unique_ptr<Doc> spriteSheet(exporter.exportSheet(ctx));
|
||||
base::task_token token;
|
||||
std::unique_ptr<Doc> spriteSheet(
|
||||
exporter.exportSheet(ctx, token));
|
||||
|
||||
// Sprite sheet isn't used, we just delete it.
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -17,7 +17,6 @@
|
||||
#include "app/doc.h"
|
||||
#include "app/file/file.h"
|
||||
#include "app/filename_formatter.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/restore_visible_layers.h"
|
||||
#include "app/snap_to_grid.h"
|
||||
#include "doc/images_map.h"
|
||||
@ -119,9 +118,9 @@ private:
|
||||
typedef std::shared_ptr<SampleBounds> SampleBoundsPtr;
|
||||
|
||||
DocExporter::Item::Item(Doc* doc,
|
||||
doc::Tag* tag,
|
||||
doc::SelectedLayers* selLayers,
|
||||
doc::SelectedFrames* selFrames)
|
||||
const doc::Tag* tag,
|
||||
const doc::SelectedLayers* selLayers,
|
||||
const doc::SelectedFrames* selFrames)
|
||||
: doc(doc)
|
||||
, tag(tag)
|
||||
, selLayers(selLayers ? new doc::SelectedLayers(*selLayers): nullptr)
|
||||
@ -176,11 +175,16 @@ doc::SelectedFrames DocExporter::Item::getSelectedFrames() const
|
||||
class DocExporter::Sample {
|
||||
public:
|
||||
Sample(Doc* document, Sprite* sprite, SelectedLayers* selLayers,
|
||||
frame_t frame, const std::string& filename, int innerPadding, bool extrude) :
|
||||
frame_t frame,
|
||||
const Tag* tag,
|
||||
const std::string& filename,
|
||||
const int innerPadding,
|
||||
const bool extrude) :
|
||||
m_document(document),
|
||||
m_sprite(sprite),
|
||||
m_selLayers(selLayers),
|
||||
m_frame(frame),
|
||||
m_tag(tag),
|
||||
m_filename(filename),
|
||||
m_innerPadding(innerPadding),
|
||||
m_extrude(extrude),
|
||||
@ -195,6 +199,7 @@ public:
|
||||
return (m_selLayers && m_selLayers->size() == 1 ? *m_selLayers->begin():
|
||||
nullptr);
|
||||
}
|
||||
const Tag* tag() const { return m_tag; }
|
||||
SelectedLayers* selectedLayers() const { return m_selLayers; }
|
||||
frame_t frame() const { return m_frame; }
|
||||
std::string filename() const { return m_filename; }
|
||||
@ -257,7 +262,10 @@ public:
|
||||
*m_selLayers);
|
||||
|
||||
render::Render render;
|
||||
render.setNewBlend(Preferences::instance().experimental.newBlend());
|
||||
|
||||
// 1) We cannot use the Preferences because this is called from a non-UI thread
|
||||
// 2) We should use the new blend mode always when we're saving files
|
||||
//render.setNewBlend(Preferences::instance().experimental.newBlend());
|
||||
|
||||
if (extrude) {
|
||||
const gfx::Rect& trim = trimmedBounds();
|
||||
@ -296,6 +304,7 @@ private:
|
||||
Sprite* m_sprite;
|
||||
SelectedLayers* m_selLayers;
|
||||
frame_t m_frame;
|
||||
const Tag* m_tag;
|
||||
std::string m_filename;
|
||||
int m_innerPadding;
|
||||
bool m_extrude;
|
||||
@ -311,6 +320,7 @@ public:
|
||||
typedef List::const_iterator const_iterator;
|
||||
|
||||
bool empty() const { return m_samples.empty(); }
|
||||
int size() const { return int(m_samples.size()); }
|
||||
|
||||
void addSample(const Sample& sample) {
|
||||
m_samples.push_back(sample);
|
||||
@ -332,45 +342,88 @@ private:
|
||||
class DocExporter::LayoutSamples {
|
||||
public:
|
||||
virtual ~LayoutSamples() { }
|
||||
virtual void layoutSamples(Samples& samples, int borderPadding, int shapePadding, int& width, int& height) = 0;
|
||||
virtual void layoutSamples(Samples& samples,
|
||||
int borderPadding,
|
||||
int shapePadding,
|
||||
int& width, int& height,
|
||||
base::task_token& token) = 0;
|
||||
};
|
||||
|
||||
class DocExporter::SimpleLayoutSamples : public DocExporter::LayoutSamples {
|
||||
public:
|
||||
SimpleLayoutSamples(SpriteSheetType type)
|
||||
: m_type(type) {
|
||||
SimpleLayoutSamples(SpriteSheetType type,
|
||||
int maxCols, int maxRows)
|
||||
: m_type(type)
|
||||
, m_maxCols(maxCols)
|
||||
, m_maxRows(maxRows) {
|
||||
}
|
||||
|
||||
void layoutSamples(Samples& samples, int borderPadding, int shapePadding, int& width, int& height) override {
|
||||
const Sprite* oldSprite = NULL;
|
||||
const Layer* oldLayer = NULL;
|
||||
void layoutSamples(Samples& samples,
|
||||
int borderPadding,
|
||||
int shapePadding,
|
||||
int& width, int& height,
|
||||
base::task_token& token) override {
|
||||
DX_TRACE("SimpleLayoutSamples type", (int)m_type, width, height);
|
||||
|
||||
const bool breakBands =
|
||||
(m_type == SpriteSheetType::Columns ||
|
||||
m_type == SpriteSheetType::Rows);
|
||||
|
||||
const Sprite* oldSprite = nullptr;
|
||||
const Layer* oldLayer = nullptr;
|
||||
const Tag* oldTag = nullptr;
|
||||
|
||||
gfx::Point framePt(borderPadding, borderPadding);
|
||||
gfx::Size rowSize(0, 0);
|
||||
|
||||
int i = 0;
|
||||
int itemInBand = 0;
|
||||
int itemsPerBand = -1;
|
||||
if (breakBands) {
|
||||
if (m_type == SpriteSheetType::Columns && m_maxRows > 0)
|
||||
itemsPerBand = m_maxRows;
|
||||
if (m_type == SpriteSheetType::Rows && m_maxCols > 0)
|
||||
itemsPerBand = m_maxCols;
|
||||
}
|
||||
|
||||
for (auto& sample : samples) {
|
||||
if (sample.isLinked())
|
||||
if (token.canceled())
|
||||
return;
|
||||
token.set_progress(0.2f + 0.2f * i / samples.size());
|
||||
|
||||
if (sample.isLinked()) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sample.isEmpty()) {
|
||||
sample.setInTextureBounds(gfx::Rect(0, 0, 0, 0));
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
const Sprite* sprite = sample.sprite();
|
||||
const Layer* layer = sample.layer();
|
||||
const Tag* tag = sample.tag();
|
||||
gfx::Size size = sample.requiredSize();
|
||||
|
||||
if (oldSprite) {
|
||||
if (breakBands && oldSprite) {
|
||||
const bool nextBand =
|
||||
(oldSprite != sprite ||
|
||||
oldLayer != layer ||
|
||||
oldTag != tag ||
|
||||
itemInBand == itemsPerBand);
|
||||
|
||||
if (m_type == SpriteSheetType::Columns) {
|
||||
// If the user didn't specify a height for the texture, we
|
||||
// put each sprite/layer in a different column.
|
||||
if (height == 0) {
|
||||
// New sprite or layer, go to next column.
|
||||
if (oldSprite != sprite || oldLayer != layer) {
|
||||
if (nextBand) {
|
||||
framePt.x += rowSize.w + shapePadding;
|
||||
framePt.y = borderPadding;
|
||||
rowSize = size;
|
||||
itemInBand = 0;
|
||||
}
|
||||
}
|
||||
// When a texture height is specified, we can put different
|
||||
@ -382,15 +435,16 @@ public:
|
||||
rowSize = size;
|
||||
}
|
||||
}
|
||||
else {
|
||||
else if (m_type == SpriteSheetType::Rows) {
|
||||
// If the user didn't specify a width for the texture, we put
|
||||
// each sprite/layer in a different row.
|
||||
if (width == 0) {
|
||||
// New sprite or layer, go to next row.
|
||||
if (oldSprite != sprite || oldLayer != layer) {
|
||||
if (nextBand) {
|
||||
framePt.x = borderPadding;
|
||||
framePt.y += rowSize.h + shapePadding;
|
||||
rowSize = size;
|
||||
itemInBand = 0;
|
||||
}
|
||||
}
|
||||
// When a texture width is specified, we can put different
|
||||
@ -402,15 +456,20 @@ public:
|
||||
rowSize = size;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
sample.setInTextureBounds(gfx::Rect(framePt, size));
|
||||
|
||||
// Next frame position.
|
||||
if (m_type == SpriteSheetType::Columns) {
|
||||
if (m_type == SpriteSheetType::Vertical ||
|
||||
m_type == SpriteSheetType::Columns) {
|
||||
framePt.y += size.h + shapePadding;
|
||||
}
|
||||
else {
|
||||
else if (m_type == SpriteSheetType::Horizontal ||
|
||||
m_type == SpriteSheetType::Rows) {
|
||||
framePt.x += size.w + shapePadding;
|
||||
}
|
||||
|
||||
@ -418,32 +477,47 @@ public:
|
||||
|
||||
oldSprite = sprite;
|
||||
oldLayer = layer;
|
||||
oldTag = tag;
|
||||
++itemInBand;
|
||||
++i;
|
||||
}
|
||||
|
||||
DX_TRACE("-> SimpleLayoutSamples", width, height);
|
||||
}
|
||||
|
||||
private:
|
||||
SpriteSheetType m_type;
|
||||
int m_maxCols;
|
||||
int m_maxRows;
|
||||
};
|
||||
|
||||
class DocExporter::BestFitLayoutSamples : public DocExporter::LayoutSamples {
|
||||
ImageBufferPtr m_imageBuf;
|
||||
public:
|
||||
BestFitLayoutSamples(ImageBufferPtr& buf)
|
||||
: m_imageBuf(buf) {
|
||||
}
|
||||
void layoutSamples(Samples& samples, int borderPadding, int shapePadding, int& width, int& height) override {
|
||||
void layoutSamples(Samples& samples,
|
||||
int borderPadding,
|
||||
int shapePadding,
|
||||
int& width, int& height,
|
||||
base::task_token& token) override {
|
||||
gfx::PackingRects pr(borderPadding, shapePadding);
|
||||
doc::ImagesMap duplicates;
|
||||
|
||||
uint32_t i = 0;
|
||||
for (auto& sample : samples) {
|
||||
if (token.canceled())
|
||||
return;
|
||||
token.set_progress_range(0.2f, 0.3f);
|
||||
token.set_progress(float(i) / samples.size());
|
||||
|
||||
if (sample.isLinked() ||
|
||||
sample.isEmpty()) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
ImageRef sampleRender(sample.createRender(m_imageBuf));
|
||||
// We have to use one ImageBuffer for each image because we're
|
||||
// going to store all images in the "duplicates" map.
|
||||
doc::ImageBufferPtr sampleBuf = std::make_shared<doc::ImageBuffer>();
|
||||
doc::ImageRef sampleRender(sample.createRender(sampleBuf));
|
||||
auto it = duplicates.find(sampleRender);
|
||||
if (it != duplicates.end()) {
|
||||
const uint32_t j = it->second;
|
||||
@ -458,13 +532,16 @@ public:
|
||||
++i;
|
||||
}
|
||||
|
||||
token.set_progress_range(0.3f, 0.4f);
|
||||
if (width == 0 || height == 0) {
|
||||
gfx::Size sz = pr.bestFit();
|
||||
gfx::Size sz = pr.bestFit(token, width, height);
|
||||
width = sz.w;
|
||||
height = sz.h;
|
||||
}
|
||||
else
|
||||
pr.pack(gfx::Size(width, height));
|
||||
else {
|
||||
pr.pack(gfx::Size(width, height), token);
|
||||
}
|
||||
token.set_progress_range(0.0f, 1.0f);
|
||||
|
||||
auto it = pr.begin();
|
||||
for (auto& sample : samples) {
|
||||
@ -480,24 +557,45 @@ public:
|
||||
};
|
||||
|
||||
DocExporter::DocExporter()
|
||||
: m_dataFormat(SpriteSheetDataFormat::Default)
|
||||
, m_textureWidth(0)
|
||||
, m_textureHeight(0)
|
||||
, m_sheetType(SpriteSheetType::None)
|
||||
, m_ignoreEmptyCels(false)
|
||||
, m_borderPadding(0)
|
||||
, m_shapePadding(0)
|
||||
, m_innerPadding(0)
|
||||
, m_trimCels(false)
|
||||
, m_trimByGrid(false)
|
||||
, m_extrude(false)
|
||||
, m_listTags(false)
|
||||
, m_listLayers(false)
|
||||
, m_listSlices(false)
|
||||
: m_docBuf(std::make_shared<doc::ImageBuffer>())
|
||||
, m_sampleBuf(std::make_shared<doc::ImageBuffer>())
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
Doc* DocExporter::exportSheet(Context* ctx)
|
||||
void DocExporter::reset()
|
||||
{
|
||||
m_sheetType = SpriteSheetType::None;
|
||||
m_dataFormat = SpriteSheetDataFormat::Default;
|
||||
m_dataFilename.clear();
|
||||
m_textureFilename.clear();
|
||||
m_filenameFormat.clear();
|
||||
m_textureWidth = 0;
|
||||
m_textureHeight = 0;
|
||||
m_textureColumns = 0;
|
||||
m_textureRows = 0;
|
||||
m_borderPadding = 0;
|
||||
m_shapePadding = 0;
|
||||
m_innerPadding = 0;
|
||||
m_ignoreEmptyCels = false;
|
||||
m_mergeDuplicates = false;
|
||||
m_trimSprite = false;
|
||||
m_trimCels = false;
|
||||
m_trimByGrid = false;
|
||||
m_extrude = false;
|
||||
m_listTags = false;
|
||||
m_listLayers = false;
|
||||
m_listSlices = false;
|
||||
m_documents.clear();
|
||||
m_tagDelta.clear();
|
||||
}
|
||||
|
||||
void DocExporter::setDocImageBuffer(const doc::ImageBufferPtr& docBuf)
|
||||
{
|
||||
m_docBuf = docBuf;
|
||||
}
|
||||
|
||||
Doc* DocExporter::exportSheet(Context* ctx, base::task_token& token)
|
||||
{
|
||||
// We output the metadata to std::cout if the user didn't specify a file.
|
||||
std::ofstream fos;
|
||||
@ -530,59 +628,191 @@ Doc* DocExporter::exportSheet(Context* ctx)
|
||||
// Steps for sheet construction:
|
||||
// 1) Capture the samples (each sprite+frame pair)
|
||||
Samples samples;
|
||||
captureSamples(samples);
|
||||
captureSamples(samples, token);
|
||||
if (samples.empty()) {
|
||||
Console console;
|
||||
console.printf("No documents to export");
|
||||
return nullptr;
|
||||
}
|
||||
if (token.canceled())
|
||||
return nullptr;
|
||||
token.set_progress(0.2f);
|
||||
|
||||
// 2) Layout those samples in a texture field.
|
||||
layoutSamples(samples);
|
||||
layoutSamples(samples, token);
|
||||
if (token.canceled())
|
||||
return nullptr;
|
||||
token.set_progress(0.4f);
|
||||
|
||||
// 3) Create and render the texture.
|
||||
std::unique_ptr<Doc> textureDocument(
|
||||
createEmptyTexture(samples));
|
||||
createEmptyTexture(samples, token));
|
||||
if (token.canceled())
|
||||
return nullptr;
|
||||
token.set_progress(0.6f);
|
||||
|
||||
Sprite* texture = textureDocument->sprite();
|
||||
Image* textureImage = texture->root()->firstLayer()
|
||||
->cel(frame_t(0))->image();
|
||||
|
||||
renderTexture(ctx, samples, textureImage);
|
||||
renderTexture(ctx, samples, textureImage, token);
|
||||
if (token.canceled())
|
||||
return nullptr;
|
||||
token.set_progress(0.8f);
|
||||
|
||||
// Trim texture
|
||||
if (m_trimSprite || m_trimCels)
|
||||
trimTexture(samples, texture);
|
||||
token.set_progress(0.9f);
|
||||
|
||||
// Save the metadata.
|
||||
if (osbuf)
|
||||
createDataFile(samples, os, textureImage);
|
||||
createDataFile(samples, os, texture);
|
||||
token.set_progress(0.95f);
|
||||
|
||||
// Save the image files.
|
||||
if (!m_textureFilename.empty()) {
|
||||
DX_TRACE("DocExporter::exportSheet", m_textureFilename);
|
||||
textureDocument->setFilename(m_textureFilename.c_str());
|
||||
int ret = save_document(ctx, textureDocument.get());
|
||||
if (ret == 0)
|
||||
textureDocument->markAsSaved();
|
||||
}
|
||||
|
||||
token.set_progress(1.0f);
|
||||
|
||||
return textureDocument.release();
|
||||
}
|
||||
|
||||
gfx::Size DocExporter::calculateSheetSize()
|
||||
{
|
||||
base::task_token token;
|
||||
Samples samples;
|
||||
captureSamples(samples);
|
||||
layoutSamples(samples);
|
||||
return calculateSheetSize(samples);
|
||||
captureSamples(samples, token);
|
||||
layoutSamples(samples, token);
|
||||
return calculateSheetSize(samples, token);
|
||||
}
|
||||
|
||||
void DocExporter::captureSamples(Samples& samples)
|
||||
void DocExporter::addDocument(
|
||||
Doc* doc,
|
||||
const doc::Tag* tag,
|
||||
const doc::SelectedLayers* selLayers,
|
||||
const doc::SelectedFrames* selFrames)
|
||||
{
|
||||
DX_TRACE("DocExporter::addDocument doc=", doc, "tag=", tag);
|
||||
|
||||
m_documents.push_back(Item(doc, tag, selLayers, selFrames));
|
||||
}
|
||||
|
||||
int DocExporter::addDocumentSamples(
|
||||
Doc* doc,
|
||||
const doc::Tag* thisTag,
|
||||
const bool splitLayers,
|
||||
const bool splitTags,
|
||||
const doc::SelectedLayers* selLayers,
|
||||
const doc::SelectedFrames* selFrames)
|
||||
{
|
||||
DX_TRACE("DocExporter::addDocumentSamples");
|
||||
|
||||
std::vector<const Tag*> tags;
|
||||
|
||||
if (thisTag)
|
||||
tags.push_back(thisTag);
|
||||
else if (splitTags) {
|
||||
if (selFrames) {
|
||||
const Tag* oldTag = nullptr;
|
||||
for (frame_t frame : *selFrames) {
|
||||
const Tag* tag = doc->sprite()->tags().innerTag(frame);
|
||||
if (oldTag != tag) {
|
||||
oldTag = tag;
|
||||
tags.push_back(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const Tag* tag : doc->sprite()->tags())
|
||||
tags.push_back(tag);
|
||||
}
|
||||
if (tags.empty())
|
||||
tags.push_back(nullptr);
|
||||
}
|
||||
else {
|
||||
tags.push_back(nullptr);
|
||||
}
|
||||
|
||||
doc::SelectedFrames selFramesTmp;
|
||||
int items = 0;
|
||||
for (const Tag* tag : tags) {
|
||||
const doc::SelectedFrames* thisSelFrames = nullptr;
|
||||
|
||||
if (selFrames) {
|
||||
if (tag) {
|
||||
selFramesTmp.clear();
|
||||
for (frame_t frame=tag->fromFrame(); frame<=tag->toFrame(); ++frame) {
|
||||
if (selFrames->contains(frame))
|
||||
selFramesTmp.insert(frame);
|
||||
}
|
||||
thisSelFrames = &selFramesTmp;
|
||||
}
|
||||
else {
|
||||
selFramesTmp = *selFrames;
|
||||
thisSelFrames = &selFramesTmp;
|
||||
}
|
||||
}
|
||||
else if (tag) {
|
||||
ASSERT(tag);
|
||||
selFramesTmp.clear();
|
||||
selFramesTmp.insert(tag->fromFrame(),
|
||||
tag->toFrame());
|
||||
thisSelFrames = &selFramesTmp;
|
||||
}
|
||||
|
||||
if (splitLayers) {
|
||||
if (selLayers) {
|
||||
for (auto layer : selLayers->toAllLayersList()) {
|
||||
if (layer->isGroup()) // Ignore groups
|
||||
continue;
|
||||
|
||||
SelectedLayers oneLayer;
|
||||
oneLayer.insert(layer);
|
||||
addDocument(doc, tag, &oneLayer, thisSelFrames);
|
||||
++items;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (auto layer : doc->sprite()->allVisibleLayers()) {
|
||||
if (layer->isGroup()) // Ignore groups
|
||||
continue;
|
||||
|
||||
SelectedLayers oneLayer;
|
||||
oneLayer.insert(layer);
|
||||
addDocument(doc, tag, &oneLayer, thisSelFrames);
|
||||
++items;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
addDocument(doc, tag, selLayers, thisSelFrames);
|
||||
++items;
|
||||
}
|
||||
}
|
||||
return std::max(1, items);
|
||||
}
|
||||
|
||||
void DocExporter::captureSamples(Samples& samples,
|
||||
base::task_token& token)
|
||||
{
|
||||
DX_TRACE("DX: Capture samples");
|
||||
|
||||
for (auto& item : m_documents) {
|
||||
if (token.canceled())
|
||||
return;
|
||||
|
||||
Doc* doc = item.doc;
|
||||
Sprite* sprite = doc->sprite();
|
||||
Layer* layer = (item.selLayers && item.selLayers->size() == 1 ?
|
||||
*item.selLayers->begin(): nullptr);
|
||||
Tag* tag = item.tag;
|
||||
const Tag* tag = item.tag;
|
||||
int frames = item.frames();
|
||||
|
||||
DX_TRACE("DX: - Item:", doc->filename(),
|
||||
@ -601,8 +831,11 @@ void DocExporter::captureSamples(Samples& samples)
|
||||
|
||||
frame_t outputFrame = 0;
|
||||
for (frame_t frame : item.getSelectedFrames()) {
|
||||
Tag* innerTag = (tag ? tag: sprite->tags().innerTag(frame));
|
||||
Tag* outerTag = sprite->tags().outerTag(frame);
|
||||
if (token.canceled())
|
||||
return;
|
||||
|
||||
const Tag* innerTag = (tag ? tag: sprite->tags().innerTag(frame));
|
||||
const Tag* outerTag = sprite->tags().outerTag(frame);
|
||||
FilenameInfo fnInfo;
|
||||
fnInfo
|
||||
.filename(doc->filename())
|
||||
@ -617,7 +850,10 @@ void DocExporter::captureSamples(Samples& samples)
|
||||
|
||||
std::string filename = filename_formatter(format, fnInfo);
|
||||
|
||||
Sample sample(doc, sprite, item.selLayers, frame, filename, m_innerPadding, m_extrude);
|
||||
Sample sample(
|
||||
doc, sprite, item.selLayers, frame,
|
||||
(innerTag && is_tag_in_filename_format(format) ? innerTag: nullptr),
|
||||
filename, m_innerPadding, m_extrude);
|
||||
Cel* cel = nullptr;
|
||||
Cel* link = nullptr;
|
||||
bool done = false;
|
||||
@ -629,8 +865,11 @@ void DocExporter::captureSamples(Samples& samples)
|
||||
}
|
||||
|
||||
// Re-use linked samples
|
||||
if (link) {
|
||||
if (link && mergeDuplicates()) {
|
||||
for (const Sample& other : samples) {
|
||||
if (token.canceled())
|
||||
return;
|
||||
|
||||
if (other.sprite() == sprite &&
|
||||
other.layer() == layer &&
|
||||
other.frame() == link->frame()) {
|
||||
@ -652,7 +891,7 @@ void DocExporter::captureSamples(Samples& samples)
|
||||
if (layer && layer->isImage() && !cel && m_ignoreEmptyCels)
|
||||
continue;
|
||||
|
||||
ImageRef sampleRender(sample.createRender(m_sampleRenderBuf));
|
||||
ImageRef sampleRender(sample.createRender(m_sampleBuf));
|
||||
|
||||
gfx::Rect frameBounds;
|
||||
doc::color_t refColor = 0;
|
||||
@ -724,31 +963,43 @@ void DocExporter::captureSamples(Samples& samples)
|
||||
}
|
||||
}
|
||||
|
||||
void DocExporter::layoutSamples(Samples& samples)
|
||||
void DocExporter::layoutSamples(Samples& samples,
|
||||
base::task_token& token)
|
||||
{
|
||||
int width = m_textureWidth;
|
||||
int height = m_textureHeight;
|
||||
|
||||
switch (m_sheetType) {
|
||||
case SpriteSheetType::Packed: {
|
||||
BestFitLayoutSamples layout(m_sampleRenderBuf);
|
||||
BestFitLayoutSamples layout;
|
||||
layout.layoutSamples(
|
||||
samples, m_borderPadding, m_shapePadding,
|
||||
m_textureWidth, m_textureHeight);
|
||||
width, height, token);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
SimpleLayoutSamples layout(m_sheetType);
|
||||
SimpleLayoutSamples layout(
|
||||
m_sheetType, m_textureColumns, m_textureRows);
|
||||
layout.layoutSamples(
|
||||
samples, m_borderPadding, m_shapePadding,
|
||||
m_textureWidth, m_textureHeight);
|
||||
width, height, token);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gfx::Size DocExporter::calculateSheetSize(const Samples& samples) const
|
||||
gfx::Size DocExporter::calculateSheetSize(const Samples& samples,
|
||||
base::task_token& token) const
|
||||
{
|
||||
DX_TRACE("DX: calculateSheetSize predefined texture size",
|
||||
m_textureWidth, m_textureHeight);
|
||||
|
||||
gfx::Rect fullTextureBounds(0, 0, m_textureWidth, m_textureHeight);
|
||||
|
||||
for (const auto& sample : samples) {
|
||||
if (token.canceled())
|
||||
return gfx::Size(0, 0);
|
||||
|
||||
if (sample.isLinked() ||
|
||||
sample.isDuplicated() ||
|
||||
sample.isEmpty())
|
||||
@ -772,11 +1023,16 @@ gfx::Size DocExporter::calculateSheetSize(const Samples& samples) const
|
||||
if (m_textureWidth == 0) fullTextureBounds.w += m_borderPadding;
|
||||
if (m_textureHeight == 0) fullTextureBounds.h += m_borderPadding;
|
||||
|
||||
DX_TRACE("DX: calculateSheetSize -> ",
|
||||
fullTextureBounds.x+fullTextureBounds.w,
|
||||
fullTextureBounds.y+fullTextureBounds.h);
|
||||
|
||||
return gfx::Size(fullTextureBounds.x+fullTextureBounds.w,
|
||||
fullTextureBounds.y+fullTextureBounds.h);
|
||||
}
|
||||
|
||||
Doc* DocExporter::createEmptyTexture(const Samples& samples) const
|
||||
Doc* DocExporter::createEmptyTexture(const Samples& samples,
|
||||
base::task_token& token) const
|
||||
{
|
||||
ColorMode colorMode = ColorMode::INDEXED;
|
||||
Palette* palette = nullptr;
|
||||
@ -785,6 +1041,9 @@ Doc* DocExporter::createEmptyTexture(const Samples& samples) const
|
||||
color_t transparentColor = 0;
|
||||
|
||||
for (const auto& sample : samples) {
|
||||
if (token.canceled())
|
||||
return nullptr;
|
||||
|
||||
if (sample.isLinked() ||
|
||||
sample.isDuplicated() ||
|
||||
sample.isEmpty())
|
||||
@ -818,18 +1077,22 @@ Doc* DocExporter::createEmptyTexture(const Samples& samples) const
|
||||
}
|
||||
}
|
||||
|
||||
gfx::Size textureSize = calculateSheetSize(samples);
|
||||
gfx::Size textureSize = calculateSheetSize(samples, token);
|
||||
if (token.canceled())
|
||||
return nullptr;
|
||||
|
||||
std::unique_ptr<Sprite> sprite(
|
||||
Sprite::MakeStdSprite(
|
||||
// TODO calculate a proper transparent color for the sprite sheet
|
||||
ImageSpec(colorMode, textureSize.w, textureSize.h, 0,
|
||||
ImageSpec(colorMode,
|
||||
std::max(textureSize.w, m_textureWidth),
|
||||
std::max(textureSize.h, m_textureHeight),
|
||||
transparentColor,
|
||||
(colorSpace ? colorSpace: gfx::ColorSpace::MakeNone())),
|
||||
maxColors));
|
||||
maxColors,
|
||||
m_docBuf));
|
||||
|
||||
if (palette)
|
||||
sprite->setPalette(palette, false);
|
||||
sprite->setTransparentColor(transparentColor);
|
||||
|
||||
std::unique_ptr<Doc> document(new Doc(sprite.get()));
|
||||
sprite.release();
|
||||
@ -837,15 +1100,25 @@ Doc* DocExporter::createEmptyTexture(const Samples& samples) const
|
||||
return document.release();
|
||||
}
|
||||
|
||||
void DocExporter::renderTexture(Context* ctx, const Samples& samples, Image* textureImage) const
|
||||
void DocExporter::renderTexture(Context* ctx,
|
||||
const Samples& samples,
|
||||
Image* textureImage,
|
||||
base::task_token& token) const
|
||||
{
|
||||
textureImage->clear(0);
|
||||
|
||||
int i = 0;
|
||||
for (const auto& sample : samples) {
|
||||
if (token.canceled())
|
||||
return;
|
||||
token.set_progress(0.6f + 0.2f * i / int(samples.size()));
|
||||
|
||||
if (sample.isLinked() ||
|
||||
sample.isDuplicated() ||
|
||||
sample.isEmpty())
|
||||
sample.isEmpty()) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make the sprite compatible with the texture so the render()
|
||||
// works correctly.
|
||||
@ -863,10 +1136,44 @@ void DocExporter::renderTexture(Context* ctx, const Samples& samples, Image* tex
|
||||
sample.inTextureBounds().x+m_innerPadding,
|
||||
sample.inTextureBounds().y+m_innerPadding,
|
||||
m_extrude);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
void DocExporter::createDataFile(const Samples& samples, std::ostream& os, Image* textureImage)
|
||||
void DocExporter::trimTexture(const Samples& samples,
|
||||
doc::Sprite* texture) const
|
||||
{
|
||||
if (m_textureWidth > 0 && m_textureHeight > 0)
|
||||
return;
|
||||
|
||||
gfx::Size size = texture->size();
|
||||
gfx::Rect bounds(0, 0, 1, 1);
|
||||
|
||||
for (const auto& sample : samples) {
|
||||
if (sample.isLinked() ||
|
||||
sample.isDuplicated() ||
|
||||
sample.isEmpty())
|
||||
continue;
|
||||
|
||||
bounds |= sample.inTextureBounds();
|
||||
}
|
||||
|
||||
if (m_textureWidth == 0) {
|
||||
ASSERT(size.w >= bounds.w);
|
||||
size.w = bounds.w;
|
||||
}
|
||||
if (m_textureHeight == 0) {
|
||||
ASSERT(size.h >= bounds.h);
|
||||
size.h = bounds.h;
|
||||
}
|
||||
|
||||
texture->setSize(m_textureWidth > 0 ? m_textureWidth: size.w,
|
||||
m_textureHeight > 0 ? m_textureHeight: size.h);
|
||||
}
|
||||
|
||||
void DocExporter::createDataFile(const Samples& samples,
|
||||
std::ostream& os,
|
||||
doc::Sprite* texture)
|
||||
{
|
||||
std::string frames_begin;
|
||||
std::string frames_end;
|
||||
@ -950,10 +1257,10 @@ void DocExporter::createDataFile(const Samples& samples, std::ostream& os, Image
|
||||
<< escape_for_json(base::get_file_name(m_textureFilename)).c_str()
|
||||
<< "\",\n";
|
||||
|
||||
os << " \"format\": \"" << (textureImage->pixelFormat() == IMAGE_RGB ? "RGBA8888": "I8") << "\",\n"
|
||||
os << " \"format\": \"" << (texture->pixelFormat() == IMAGE_RGB ? "RGBA8888": "I8") << "\",\n"
|
||||
<< " \"size\": { "
|
||||
<< "\"w\": " << textureImage->width() << ", "
|
||||
<< "\"h\": " << textureImage->height() << " },\n"
|
||||
<< "\"w\": " << texture->width() << ", "
|
||||
<< "\"h\": " << texture->height() << " },\n"
|
||||
<< " \"scale\": \"1\"";
|
||||
|
||||
// meta.frameTags
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "app/sprite_sheet_data_format.h"
|
||||
#include "app/sprite_sheet_type.h"
|
||||
#include "base/disable_copying.h"
|
||||
#include "base/task.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/image_buffer.h"
|
||||
#include "doc/object_id.h"
|
||||
@ -27,6 +28,7 @@ namespace doc {
|
||||
class Image;
|
||||
class SelectedFrames;
|
||||
class SelectedLayers;
|
||||
class Sprite;
|
||||
class Tag;
|
||||
}
|
||||
|
||||
@ -39,33 +41,41 @@ namespace app {
|
||||
public:
|
||||
DocExporter();
|
||||
|
||||
void reset();
|
||||
void setDocImageBuffer(const doc::ImageBufferPtr& docBuf);
|
||||
|
||||
SpriteSheetDataFormat dataFormat() const { return m_dataFormat; }
|
||||
const std::string& dataFilename() { return m_dataFilename; }
|
||||
const std::string& textureFilename() { return m_textureFilename; }
|
||||
int textureWidth() const { return m_textureWidth; }
|
||||
int textureHeight() const { return m_textureHeight; }
|
||||
// int textureWidth() const { return m_textureWidth; }
|
||||
// int textureHeight() const { return m_textureHeight; }
|
||||
SpriteSheetType spriteSheetType() { return m_sheetType; }
|
||||
bool ignoreEmptyCels() { return m_ignoreEmptyCels; }
|
||||
int borderPadding() const { return m_borderPadding; }
|
||||
int shapePadding() const { return m_shapePadding; }
|
||||
int innerPadding() const { return m_innerPadding; }
|
||||
bool trimCels() const { return m_trimCels; }
|
||||
bool trimByGrid() const { return m_trimByGrid; }
|
||||
bool extrude() const { return m_extrude; }
|
||||
// bool ignoreEmptyCels() { return m_ignoreEmptyCels; }
|
||||
// int borderPadding() const { return m_borderPadding; }
|
||||
// int shapePadding() const { return m_shapePadding; }
|
||||
// int innerPadding() const { return m_innerPadding; }
|
||||
// bool trimSprite() const { return m_trimSprite; }
|
||||
// bool trimCels() const { return m_trimCels; }
|
||||
// bool trimByGrid() const { return m_trimByGrid; }
|
||||
// bool extrude() const { return m_extrude; }
|
||||
const std::string& filenameFormat() const { return m_filenameFormat; }
|
||||
bool listTags() const { return m_listTags; }
|
||||
bool listLayers() const { return m_listLayers; }
|
||||
// bool listTags() const { return m_listTags; }
|
||||
// bool listLayers() const { return m_listLayers; }
|
||||
|
||||
void setDataFormat(SpriteSheetDataFormat format) { m_dataFormat = format; }
|
||||
void setDataFilename(const std::string& filename) { m_dataFilename = filename; }
|
||||
void setTextureFilename(const std::string& filename) { m_textureFilename = filename; }
|
||||
void setTextureWidth(int width) { m_textureWidth = width; }
|
||||
void setTextureHeight(int height) { m_textureHeight = height; }
|
||||
void setTextureColumns(int columns) { m_textureColumns = columns; }
|
||||
void setTextureRows(int rows) { m_textureRows = rows; }
|
||||
void setSpriteSheetType(SpriteSheetType type) { m_sheetType = type; }
|
||||
void setIgnoreEmptyCels(bool ignore) { m_ignoreEmptyCels = ignore; }
|
||||
void setMergeDuplicates(bool merge) { m_mergeDuplicates = merge; }
|
||||
void setBorderPadding(int padding) { m_borderPadding = padding; }
|
||||
void setShapePadding(int padding) { m_shapePadding = padding; }
|
||||
void setInnerPadding(int padding) { m_innerPadding = padding; }
|
||||
void setTrimSprite(bool trim) { m_trimSprite = trim; }
|
||||
void setTrimCels(bool trim) { m_trimCels = trim; }
|
||||
void setTrimByGrid(bool trimByGrid) { m_trimByGrid = trimByGrid; }
|
||||
void setExtrude(bool extrude) { m_extrude = extrude; }
|
||||
@ -74,14 +84,21 @@ namespace app {
|
||||
void setListLayers(bool value) { m_listLayers = value; }
|
||||
void setListSlices(bool value) { m_listSlices = value; }
|
||||
|
||||
void addDocument(Doc* document,
|
||||
doc::Tag* tag,
|
||||
doc::SelectedLayers* selLayers,
|
||||
doc::SelectedFrames* selFrames) {
|
||||
m_documents.push_back(Item(document, tag, selLayers, selFrames));
|
||||
}
|
||||
void addDocument(
|
||||
Doc* doc,
|
||||
const doc::Tag* tag,
|
||||
const doc::SelectedLayers* selLayers,
|
||||
const doc::SelectedFrames* selFrames);
|
||||
|
||||
Doc* exportSheet(Context* ctx);
|
||||
int addDocumentSamples(
|
||||
Doc* doc,
|
||||
const doc::Tag* tag,
|
||||
const bool splitLayers,
|
||||
const bool splitTags,
|
||||
const doc::SelectedLayers* selLayers,
|
||||
const doc::SelectedFrames* selFrames);
|
||||
|
||||
Doc* exportSheet(Context* ctx, base::task_token& token);
|
||||
gfx::Size calculateSheetSize();
|
||||
|
||||
private:
|
||||
@ -91,24 +108,33 @@ namespace app {
|
||||
class SimpleLayoutSamples;
|
||||
class BestFitLayoutSamples;
|
||||
|
||||
void captureSamples(Samples& samples);
|
||||
void layoutSamples(Samples& samples);
|
||||
gfx::Size calculateSheetSize(const Samples& samples) const;
|
||||
Doc* createEmptyTexture(const Samples& samples) const;
|
||||
void renderTexture(Context* ctx, const Samples& samples, doc::Image* textureImage) const;
|
||||
void createDataFile(const Samples& samples, std::ostream& os, doc::Image* textureImage);
|
||||
bool mergeDuplicates() const { return m_mergeDuplicates; }
|
||||
void captureSamples(Samples& samples,
|
||||
base::task_token& token);
|
||||
void layoutSamples(Samples& samples,
|
||||
base::task_token& token);
|
||||
gfx::Size calculateSheetSize(const Samples& samples,
|
||||
base::task_token& token) const;
|
||||
Doc* createEmptyTexture(const Samples& samples,
|
||||
base::task_token& token) const;
|
||||
void renderTexture(Context* ctx,
|
||||
const Samples& samples,
|
||||
doc::Image* textureImage,
|
||||
base::task_token& token) const;
|
||||
void trimTexture(const Samples& samples, doc::Sprite* texture) const;
|
||||
void createDataFile(const Samples& samples, std::ostream& os, doc::Sprite* texture);
|
||||
|
||||
class Item {
|
||||
public:
|
||||
Doc* doc;
|
||||
doc::Tag* tag;
|
||||
const doc::Tag* tag;
|
||||
doc::SelectedLayers* selLayers;
|
||||
doc::SelectedFrames* selFrames;
|
||||
|
||||
Item(Doc* doc,
|
||||
doc::Tag* tag,
|
||||
doc::SelectedLayers* selLayers,
|
||||
doc::SelectedFrames* selFrames);
|
||||
const doc::Tag* tag,
|
||||
const doc::SelectedLayers* selLayers,
|
||||
const doc::SelectedFrames* selFrames);
|
||||
Item(Item&& other);
|
||||
~Item();
|
||||
|
||||
@ -121,31 +147,38 @@ namespace app {
|
||||
};
|
||||
typedef std::vector<Item> Items;
|
||||
|
||||
SpriteSheetType m_sheetType;
|
||||
SpriteSheetDataFormat m_dataFormat;
|
||||
std::string m_dataFilename;
|
||||
std::string m_textureFilename;
|
||||
std::string m_filenameFormat;
|
||||
int m_textureWidth;
|
||||
int m_textureHeight;
|
||||
SpriteSheetType m_sheetType;
|
||||
bool m_ignoreEmptyCels;
|
||||
int m_textureColumns;
|
||||
int m_textureRows;
|
||||
int m_borderPadding;
|
||||
int m_shapePadding;
|
||||
int m_innerPadding;
|
||||
bool m_ignoreEmptyCels;
|
||||
bool m_mergeDuplicates;
|
||||
bool m_trimSprite;
|
||||
bool m_trimCels;
|
||||
bool m_trimByGrid;
|
||||
bool m_extrude;
|
||||
Items m_documents;
|
||||
std::string m_filenameFormat;
|
||||
doc::ImageBufferPtr m_sampleRenderBuf;
|
||||
bool m_listTags;
|
||||
bool m_listLayers;
|
||||
bool m_listSlices;
|
||||
Items m_documents;
|
||||
|
||||
// Displacement for each tag from/to frames in case we export
|
||||
// them. It's used in case we trim frames outside tags and they
|
||||
// will not be exported at all in the final result.
|
||||
std::map<doc::ObjectId, std::pair<int, int> > m_tagDelta;
|
||||
|
||||
// Buffers used
|
||||
doc::ImageBufferPtr m_docBuf;
|
||||
doc::ImageBufferPtr m_sampleBuf;
|
||||
|
||||
DISABLE_COPYING(DocExporter);
|
||||
};
|
||||
|
||||
|
@ -21,6 +21,8 @@ namespace app {
|
||||
|
||||
void run(base::task::func_t&& func);
|
||||
|
||||
// Returns true when the task is completed (whether it was
|
||||
// canceled or not)
|
||||
bool completed() const {
|
||||
return m_task.completed();
|
||||
}
|
||||
|
@ -238,6 +238,14 @@ Editor* UIContext::activeEditor()
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Editor* UIContext::getEditorFor(Doc* document)
|
||||
{
|
||||
if (auto view = getFirstDocView(document))
|
||||
return view->editor();
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool UIContext::hasClosedDocs()
|
||||
{
|
||||
return m_closedDocs.hasClosedDocs();
|
||||
|
@ -94,14 +94,15 @@ Sprite::~Sprite()
|
||||
|
||||
// static
|
||||
Sprite* Sprite::MakeStdSprite(const ImageSpec& spec,
|
||||
const int ncolors)
|
||||
const int ncolors,
|
||||
const ImageBufferPtr& imageBuf)
|
||||
{
|
||||
// Create the sprite.
|
||||
std::unique_ptr<Sprite> sprite(new Sprite(spec, ncolors));
|
||||
sprite->setTotalFrames(frame_t(1));
|
||||
|
||||
// Create the main image.
|
||||
ImageRef image(Image::create(spec));
|
||||
ImageRef image(Image::create(spec, imageBuf));
|
||||
clear_image(image.get(), 0);
|
||||
|
||||
// Create the first transparent layer.
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "doc/cel_list.h"
|
||||
#include "doc/color.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/image_buffer.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "doc/image_spec.h"
|
||||
#include "doc/layer_list.h"
|
||||
@ -62,7 +63,8 @@ namespace doc {
|
||||
// Creates a new sprite with one transparent layer and one cel
|
||||
// with an image of the size of the sprite.
|
||||
static Sprite* MakeStdSprite(const ImageSpec& spec,
|
||||
const int ncolors = 256);
|
||||
const int ncolors = 256,
|
||||
const ImageBufferPtr& imageBuf = ImageBufferPtr());
|
||||
|
||||
////////////////////////////////////////
|
||||
// Main properties
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite UI Library
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -137,12 +138,23 @@ bool ButtonBase::onProcessMessage(Message* msg)
|
||||
}
|
||||
|
||||
case kKeyUpMessage:
|
||||
if (isEnabled()) {
|
||||
if (m_behaviorType == kButtonWidget) {
|
||||
if (isSelected()) {
|
||||
generateButtonSelectSignal();
|
||||
if (isEnabled() && hasFocus()) {
|
||||
switch (m_behaviorType) {
|
||||
|
||||
case kButtonWidget:
|
||||
if (isSelected()) {
|
||||
generateButtonSelectSignal();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case kCheckWidget: {
|
||||
// Fire onClick() event
|
||||
Event ev(this);
|
||||
onClick(ev);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -620,7 +620,8 @@ void ComboBox::openListBox()
|
||||
size.w = m_button->bounds().x2() - entryBounds.x - view->border().width();
|
||||
size.h = viewport->border().height();
|
||||
for (Widget* item : m_items)
|
||||
size.h += item->sizeHint().h;
|
||||
if (!item->hasFlags(HIDDEN))
|
||||
size.h += item->sizeHint().h;
|
||||
|
||||
int max = MAX(entryBounds.y, ui::display_h() - entryBounds.y2()) - 8*guiscale();
|
||||
size.h = MID(textHeight(), size.h, max);
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite UI Library
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -10,6 +11,7 @@
|
||||
|
||||
#include "ui/listbox.h"
|
||||
|
||||
#include "base/clamp.h"
|
||||
#include "base/fs.h"
|
||||
#include "ui/listitem.h"
|
||||
#include "ui/message.h"
|
||||
@ -115,8 +117,8 @@ void ListBox::selectChild(Widget* item, Message* msg)
|
||||
ASSERT(i >= 0 && i < int(m_states.size()));
|
||||
newState = (i >= 0 && i < int(m_states.size()) ? m_states[i]: false);
|
||||
|
||||
if (i >= MIN(itemIndex, m_firstSelectedIndex) &&
|
||||
i <= MAX(itemIndex, m_firstSelectedIndex)) {
|
||||
if (i >= std::min(itemIndex, m_firstSelectedIndex) &&
|
||||
i <= std::max(itemIndex, m_firstSelectedIndex)) {
|
||||
newState = !newState;
|
||||
}
|
||||
}
|
||||
@ -219,13 +221,17 @@ bool ListBox::onProcessMessage(Message* msg)
|
||||
gfx::Rect vp = view->viewportBounds();
|
||||
|
||||
if (mousePos.y < vp.y) {
|
||||
int num = MAX(1, (vp.y - mousePos.y) / 8);
|
||||
selectIndex(MID(0, m_lastSelectedIndex-num, getItemsCount()-1), msg);
|
||||
const int num = std::max(1, (vp.y - mousePos.y) / 8);
|
||||
const int select =
|
||||
advanceIndexThroughVisibleItems(m_lastSelectedIndex, -num, false);
|
||||
selectIndex(select, msg);
|
||||
pick_item = false;
|
||||
}
|
||||
else if (mousePos.y >= vp.y + vp.h) {
|
||||
int num = MAX(1, (mousePos.y - (vp.y+vp.h-1)) / 8);
|
||||
selectIndex(MID(0, m_lastSelectedIndex+num, getItemsCount()-1), msg);
|
||||
const int num = std::max(1, (mousePos.y - (vp.y+vp.h-1)) / 8);
|
||||
const int select =
|
||||
advanceIndexThroughVisibleItems(m_lastSelectedIndex, +num, false);
|
||||
selectIndex(select, msg);
|
||||
pick_item = false;
|
||||
}
|
||||
}
|
||||
@ -272,7 +278,7 @@ bool ListBox::onProcessMessage(Message* msg)
|
||||
case kKeyDownMessage:
|
||||
if (hasFocus() && !children().empty()) {
|
||||
int select = getSelectedIndex();
|
||||
int bottom = MAX(0, children().size()-1);
|
||||
int bottom = std::max(0, int(children().size()-1));
|
||||
View* view = View::getView(this);
|
||||
KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
|
||||
KeyScancode scancode = keymsg->scancode();
|
||||
@ -338,7 +344,7 @@ bool ListBox::onProcessMessage(Message* msg)
|
||||
return Widget::onProcessMessage(msg);
|
||||
}
|
||||
|
||||
selectIndex(MID(0, select, bottom), msg);
|
||||
selectIndex(base::clamp(select, 0, bottom), msg);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
@ -384,7 +390,7 @@ void ListBox::onSizeHint(SizeHintEvent& ev)
|
||||
|
||||
Size reqSize = child->sizeHint();
|
||||
|
||||
w = MAX(w, reqSize.w);
|
||||
w = std::max(w, reqSize.w);
|
||||
h += reqSize.h;
|
||||
++visibles;
|
||||
}
|
||||
@ -410,11 +416,11 @@ void ListBox::onDoubleClickItem()
|
||||
int ListBox::advanceIndexThroughVisibleItems(
|
||||
int startIndex, int delta, const bool loop)
|
||||
{
|
||||
const int bottom = MAX(0, children().size()-1);
|
||||
const int bottom = std::max(0, int(children().size()-1));
|
||||
const int sgn = SGN(delta);
|
||||
int index = startIndex;
|
||||
|
||||
startIndex = MID(0, startIndex, bottom);
|
||||
startIndex = base::clamp(startIndex, 0, bottom);
|
||||
int lastVisibleIndex = startIndex;
|
||||
|
||||
bool cycle = false;
|
||||
|
Loading…
x
Reference in New Issue
Block a user