mirror of
https://github.com/aseprite/aseprite.git
synced 2025-01-27 06:35:16 +00:00
Add more options to export sprite sheet (e.g. best fit for texture)
This commit is contained in:
parent
1f25579f5a
commit
df3c16b802
@ -7,7 +7,16 @@
|
||||
<combobox id="sheet_type" cell_hspan="3" />
|
||||
|
||||
<label id="columns_label" text="Columns:" />
|
||||
<entry id="columns" text="4" maxsize="4" cell_hspan="3" />
|
||||
<entry id="columns" text="" maxsize="4" />
|
||||
<boxfiller cell_hspan="2" />
|
||||
|
||||
<label id="fit_width_label" text="Width:" />
|
||||
<combobox id="fit_width" text="" maxsize="5" editable="true" />
|
||||
<label id="fit_height_label" text="Height:" editable="true" />
|
||||
<combobox id="fit_height" text="" maxsize="5" />
|
||||
|
||||
<hbox id="best_fit_filler" />
|
||||
<check cell_hspan="3" id="best_fit" text="Best fit for texture" />
|
||||
|
||||
<label text="Export Action:" />
|
||||
<combobox id="export_action" cell_hspan="3" />
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include "app/ui/editor/editor.h"
|
||||
#include "app/undo_transaction.h"
|
||||
#include "base/bind.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "raster/cel.h"
|
||||
#include "raster/image.h"
|
||||
#include "raster/layer.h"
|
||||
@ -55,23 +56,123 @@ namespace app {
|
||||
|
||||
using namespace ui;
|
||||
|
||||
namespace {
|
||||
|
||||
struct Fit {
|
||||
int width;
|
||||
int height;
|
||||
int columns;
|
||||
int freearea;
|
||||
|
||||
Fit(int width, int height, int columns, int freearea) :
|
||||
width(width), height(height), columns(columns), freearea(freearea) {
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate best size for the given sprite
|
||||
// TODO this function was programmed in ten minutes, please optimize it
|
||||
Fit best_fit(Sprite* sprite) {
|
||||
int nframes = sprite->totalFrames();
|
||||
int framew = sprite->width();
|
||||
int frameh = sprite->height();
|
||||
Fit result(framew*nframes, frameh, nframes, INT_MAX);
|
||||
int w, h;
|
||||
|
||||
for (w=2; w < framew; w*=2)
|
||||
;
|
||||
for (h=2; h < frameh; h*=2)
|
||||
;
|
||||
|
||||
int z = 0;
|
||||
while (z++ < nframes*2) {
|
||||
gfx::Region rgn(gfx::Rect(w, h));
|
||||
int contained_frames = 0;
|
||||
bool fully_contained = false;
|
||||
|
||||
for (int v=0; v+frameh <= h && !fully_contained; v+=frameh) {
|
||||
for (int u=0; u+framew <= w; u+=framew) {
|
||||
gfx::Rect framerc = gfx::Rect(u, v, framew, frameh);
|
||||
rgn.createSubtraction(rgn, gfx::Region(framerc));
|
||||
|
||||
++contained_frames;
|
||||
if (nframes == contained_frames) {
|
||||
fully_contained = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fully_contained) {
|
||||
// TODO convert this to a template function gfx::area()
|
||||
int freearea = 0;
|
||||
for (gfx::Region::iterator it=rgn.begin(), end=rgn.end();
|
||||
it != end; ++it) {
|
||||
freearea += (*it).w * (*it).h;
|
||||
}
|
||||
|
||||
Fit fit(w, h, (w / framew), freearea);
|
||||
if (fit.freearea < result.freearea)
|
||||
result = fit;
|
||||
}
|
||||
|
||||
if (z & 1) w *= 2;
|
||||
else h *= 2;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ExportSpriteSheetWindow : public app::gen::ExportSpriteSheet {
|
||||
public:
|
||||
typedef ExportSpriteSheetCommand::SpriteSheetType SpriteSheetType;
|
||||
typedef ExportSpriteSheetCommand::ExportAction ExportAction;
|
||||
|
||||
ExportSpriteSheetWindow(Context* context)
|
||||
ExportSpriteSheetWindow(Document* doc, Sprite* sprite)
|
||||
: m_sprite(sprite)
|
||||
{
|
||||
doc::ExportDataPtr data = doc->exportData();
|
||||
|
||||
sheetType()->addItem("Horizontal Strip");
|
||||
sheetType()->addItem("Vertical Strip");
|
||||
sheetType()->addItem("Matrix");
|
||||
if (data)
|
||||
sheetType()->setSelectedItemIndex((int)data->type());
|
||||
|
||||
exportAction()->addItem("Save Copy As...");
|
||||
exportAction()->addItem("Save As...");
|
||||
exportAction()->addItem("Save");
|
||||
exportAction()->addItem("Do Not Save");
|
||||
|
||||
for (int i=2; i<=8192; i*=2) {
|
||||
std::string value = base::convert_to<std::string>(i);
|
||||
if (i >= m_sprite->width()) fitWidth()->addItem(value);
|
||||
if (i >= m_sprite->height()) fitHeight()->addItem(value);
|
||||
}
|
||||
|
||||
if (!data || data->bestFit()) {
|
||||
bestFit()->setSelected(true);
|
||||
onBestFit();
|
||||
}
|
||||
else if (data) {
|
||||
columns()->setTextf("%d", data->columns());
|
||||
onColumnsChange();
|
||||
|
||||
if (data->width() > 0) fitWidth()->getEntryWidget()->setTextf("%d", data->width());
|
||||
if (data->height() > 0) fitHeight()->getEntryWidget()->setTextf("%d", data->height());
|
||||
}
|
||||
else {
|
||||
columns()->setText("4");
|
||||
onColumnsChange();
|
||||
}
|
||||
|
||||
sheetType()->Change.connect(&ExportSpriteSheetWindow::onSheetTypeChange, this);
|
||||
columns()->EntryChange.connect(Bind<void>(&ExportSpriteSheetWindow::onColumnsChange, this));
|
||||
fitWidth()->Change.connect(Bind<void>(&ExportSpriteSheetWindow::onSizeChange, this));
|
||||
fitHeight()->Change.connect(Bind<void>(&ExportSpriteSheetWindow::onSizeChange, this));
|
||||
bestFit()->Click.connect(Bind<void>(&ExportSpriteSheetWindow::onBestFit, this));
|
||||
|
||||
onSheetTypeChange();
|
||||
}
|
||||
|
||||
@ -91,18 +192,37 @@ public:
|
||||
return columns()->getTextInt();
|
||||
}
|
||||
|
||||
int fitWidthValue() {
|
||||
return fitWidth()->getEntryWidget()->getTextInt();
|
||||
}
|
||||
|
||||
int fitHeightValue() {
|
||||
return fitHeight()->getEntryWidget()->getTextInt();
|
||||
}
|
||||
|
||||
bool bestFitValue() {
|
||||
return bestFit()->isSelected();
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void onSheetTypeChange()
|
||||
{
|
||||
void onSheetTypeChange() {
|
||||
bool col = false;
|
||||
bool state = false;
|
||||
switch (sheetType()->getSelectedItemIndex()) {
|
||||
case ExportSpriteSheetCommand::Matrix:
|
||||
state = true;
|
||||
break;
|
||||
}
|
||||
|
||||
columnsLabel()->setVisible(state);
|
||||
columns()->setVisible(state);
|
||||
fitWidthLabel()->setVisible(state);
|
||||
fitWidth()->setVisible(state);
|
||||
fitHeightLabel()->setVisible(state);
|
||||
fitHeight()->setVisible(state);
|
||||
bestFitFiller()->setVisible(state);
|
||||
bestFit()->setVisible(state);
|
||||
|
||||
gfx::Size reqSize = getPreferredSize();
|
||||
moveWindow(gfx::Rect(getOrigin(), reqSize));
|
||||
@ -110,6 +230,29 @@ protected:
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void onColumnsChange() {
|
||||
fitWidth()->getEntryWidget()->setTextf("");
|
||||
fitHeight()->getEntryWidget()->setTextf("");
|
||||
bestFit()->setSelected(false);
|
||||
}
|
||||
|
||||
void onSizeChange() {
|
||||
columns()->setTextf("");
|
||||
bestFit()->setSelected(false);
|
||||
}
|
||||
|
||||
void onBestFit() {
|
||||
if (!bestFit()->isSelected())
|
||||
return;
|
||||
|
||||
Fit fit = best_fit(m_sprite);
|
||||
columns()->setTextf("%d", fit.columns);
|
||||
fitWidth()->getEntryWidget()->setTextf("%d", fit.width);
|
||||
fitHeight()->getEntryWidget()->setTextf("%d", fit.height);
|
||||
}
|
||||
|
||||
private:
|
||||
Sprite* m_sprite;
|
||||
};
|
||||
|
||||
ExportSpriteSheetCommand::ExportSpriteSheetCommand()
|
||||
@ -127,19 +270,29 @@ bool ExportSpriteSheetCommand::onEnabled(Context* context)
|
||||
|
||||
void ExportSpriteSheetCommand::onExecute(Context* context)
|
||||
{
|
||||
Document* document(context->activeDocument());
|
||||
Sprite* sprite = document->sprite();
|
||||
|
||||
if (m_useUI) {
|
||||
ExportSpriteSheetWindow window(context);
|
||||
ExportSpriteSheetWindow window(document, sprite);
|
||||
window.openWindowInForeground();
|
||||
if (!window.ok())
|
||||
return;
|
||||
|
||||
setType(window.spriteSheetTypeValue());
|
||||
setAction(window.exportActionValue());
|
||||
setColumns(window.columnsValue());
|
||||
m_type = window.spriteSheetTypeValue();
|
||||
m_action = window.exportActionValue();
|
||||
m_columns = window.columnsValue();
|
||||
m_width = window.fitWidthValue();
|
||||
m_height= window.fitHeightValue();
|
||||
m_bestFit = window.bestFitValue();
|
||||
}
|
||||
else if (m_bestFit) {
|
||||
Fit fit = best_fit(sprite);
|
||||
m_columns = fit.columns;
|
||||
m_width = fit.width;
|
||||
m_height = fit.height;
|
||||
}
|
||||
|
||||
Document* document(context->activeDocument());
|
||||
Sprite* sprite = document->sprite();
|
||||
FrameNumber nframes = sprite->totalFrames();
|
||||
int columns;
|
||||
|
||||
@ -156,9 +309,10 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
|
||||
}
|
||||
|
||||
columns = MID(1, columns, nframes);
|
||||
int sheet_w = (m_width > 0 ? m_width : sprite->width()*columns);
|
||||
int sheet_h = (m_height > 0 ? m_height : sprite->height()*((nframes/columns)+((nframes%columns)>0?1:0)));
|
||||
columns = sheet_w / sprite->width();
|
||||
|
||||
int sheet_w = sprite->width()*columns;
|
||||
int sheet_h = sprite->height()*((nframes/columns)+((nframes%columns)>0?1:0));
|
||||
base::UniquePtr<Image> resultImage(Image::create(sprite->pixelFormat(), sheet_w, sheet_h));
|
||||
base::UniquePtr<Image> tempImage(Image::create(sprite->pixelFormat(), sprite->width(), sprite->height()));
|
||||
raster::clear_image(resultImage, 0);
|
||||
@ -297,6 +451,9 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
|
||||
doc::ExportDataPtr data(new doc::ExportData);
|
||||
data->setType(type);
|
||||
data->setColumns(columns);
|
||||
data->setWidth(m_width);
|
||||
data->setHeight(m_height);
|
||||
data->setBestFit(m_bestFit);
|
||||
data->setFilename(command->selectedFilename());
|
||||
document->setExportData(data);
|
||||
}
|
||||
@ -325,6 +482,31 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
|
||||
}
|
||||
}
|
||||
|
||||
void ExportSpriteSheetCommand::setExportData(doc::ExportDataPtr data)
|
||||
{
|
||||
ExportSpriteSheetCommand::SpriteSheetType type;
|
||||
switch (data->type()) {
|
||||
case doc::ExportData::None: return;
|
||||
case doc::ExportData::HorizontalStrip:
|
||||
type = ExportSpriteSheetCommand::HorizontalStrip;
|
||||
break;
|
||||
case doc::ExportData::VerticalStrip:
|
||||
type = ExportSpriteSheetCommand::VerticalStrip;
|
||||
break;
|
||||
case doc::ExportData::Matrix:
|
||||
type = ExportSpriteSheetCommand::Matrix;
|
||||
break;
|
||||
}
|
||||
|
||||
m_type = type;
|
||||
m_action = ExportSpriteSheetCommand::SaveCopyAs;
|
||||
m_columns = data->columns();
|
||||
m_width = data->width();
|
||||
m_height = data->height();
|
||||
m_bestFit = data->bestFit();
|
||||
m_filename = data->filename();
|
||||
}
|
||||
|
||||
Command* CommandFactory::createExportSpriteSheetCommand()
|
||||
{
|
||||
return new ExportSpriteSheetCommand;
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
#include "app/commands/command.h"
|
||||
#include "base/override.h"
|
||||
#include "doc/export_data.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
@ -39,10 +40,8 @@ namespace app {
|
||||
ExportAction action() const { return m_action; }
|
||||
|
||||
void setUseUI(bool useUI) { m_useUI = useUI; }
|
||||
void setType(SpriteSheetType type) { m_type = type; }
|
||||
void setExportData(doc::ExportDataPtr data);
|
||||
void setAction(ExportAction action) { m_action = action; }
|
||||
void setColumns(int columns) { m_columns = columns; }
|
||||
void setFileName(const std::string& filename) { m_filename = filename; }
|
||||
|
||||
protected:
|
||||
virtual bool onEnabled(Context* context) OVERRIDE;
|
||||
@ -53,6 +52,9 @@ namespace app {
|
||||
SpriteSheetType m_type;
|
||||
ExportAction m_action;
|
||||
int m_columns;
|
||||
int m_width;
|
||||
int m_height;
|
||||
bool m_bestFit;
|
||||
std::string m_filename;
|
||||
};
|
||||
|
||||
|
@ -64,26 +64,11 @@ void RepeatLastExportCommand::onExecute(Context* context)
|
||||
doc::ExportDataPtr data = document->exportData();
|
||||
|
||||
if (data != NULL) {
|
||||
if (data->type() == doc::ExportData::None)
|
||||
return; // Do nothing case
|
||||
|
||||
command->setUseUI(false);
|
||||
|
||||
ExportSpriteSheetCommand::SpriteSheetType type;
|
||||
switch (data->type()) {
|
||||
case doc::ExportData::None: return;
|
||||
case doc::ExportData::HorizontalStrip:
|
||||
type = ExportSpriteSheetCommand::HorizontalStrip;
|
||||
break;
|
||||
case doc::ExportData::VerticalStrip:
|
||||
type = ExportSpriteSheetCommand::VerticalStrip;
|
||||
break;
|
||||
case doc::ExportData::Matrix:
|
||||
type = ExportSpriteSheetCommand::Matrix;
|
||||
break;
|
||||
}
|
||||
|
||||
command->setType(type);
|
||||
command->setAction(ExportSpriteSheetCommand::SaveCopyAs);
|
||||
command->setColumns(data->columns());
|
||||
command->setFileName(data->filename());
|
||||
command->setExportData(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -218,6 +218,10 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
|
||||
else if (elem_name == "combobox") {
|
||||
if (!widget)
|
||||
widget = new ComboBox();
|
||||
|
||||
bool editable = bool_attr_is_true(elem, "editable");
|
||||
if (editable)
|
||||
((ComboBox*)widget)->setEditable(true);
|
||||
}
|
||||
else if (elem_name == "entry") {
|
||||
const char* maxsize = elem->Attribute("maxsize");
|
||||
|
@ -20,18 +20,28 @@ namespace doc {
|
||||
None,
|
||||
HorizontalStrip,
|
||||
VerticalStrip,
|
||||
Matrix
|
||||
Matrix,
|
||||
};
|
||||
|
||||
ExportData() : m_type(None) {
|
||||
m_columns = 0;
|
||||
m_width = 0;
|
||||
m_height = 0;
|
||||
m_bestFit = false;
|
||||
}
|
||||
|
||||
Type type() const { return m_type; }
|
||||
int columns() const { return m_columns; }
|
||||
int width() const { return m_width; }
|
||||
int height() const { return m_height; }
|
||||
bool bestFit() const { return m_bestFit; }
|
||||
const std::string& filename() const { return m_filename; }
|
||||
|
||||
void setType(Type type) { m_type = type; }
|
||||
void setColumns(int columns) { m_columns = columns; }
|
||||
void setWidth(int width) { m_width = width; }
|
||||
void setHeight(int height) { m_height = height; }
|
||||
void setBestFit(bool bestFit) { m_bestFit = bestFit; }
|
||||
void setFilename(const std::string& filename) {
|
||||
m_filename = filename;
|
||||
}
|
||||
@ -39,6 +49,9 @@ namespace doc {
|
||||
private:
|
||||
Type m_type;
|
||||
int m_columns;
|
||||
int m_width;
|
||||
int m_height;
|
||||
bool m_bestFit;
|
||||
std::string m_filename;
|
||||
};
|
||||
|
||||
|
@ -56,7 +56,7 @@ namespace gfx {
|
||||
reference operator*() { m_rect = *m_ptr; return m_rect; }
|
||||
private:
|
||||
Box* m_ptr;
|
||||
Rect m_rect;
|
||||
mutable Rect m_rect;
|
||||
template<typename> friend class RegionIterator;
|
||||
friend class ::gfx::Region;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user