1 // Aseprite
2 // Copyright (C) 2001-2018 David Capello
3 //
4 // This program is distributed under the terms of
5 // the End-User License Agreement for Aseprite.
6
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10
11 #include "app/app.h"
12 #include "app/commands/command.h"
13 #include "app/context.h"
14 #include "app/context_access.h"
15 #include "app/doc.h"
16 #include "app/doc_exporter.h"
17 #include "app/file/file.h"
18 #include "app/file_selector.h"
19 #include "app/i18n/strings.h"
20 #include "app/modules/editors.h"
21 #include "app/pref/preferences.h"
22 #include "app/restore_visible_layers.h"
23 #include "app/ui/editor/editor.h"
24 #include "app/ui/layer_frame_comboboxes.h"
25 #include "app/ui/optional_alert.h"
26 #include "app/ui/status_bar.h"
27 #include "app/ui/timeline/timeline.h"
28 #include "base/bind.h"
29 #include "base/convert_to.h"
30 #include "base/fs.h"
31 #include "base/string.h"
32 #include "doc/frame_tag.h"
33 #include "doc/layer.h"
34 #include "fmt/format.h"
35
36 #include "export_sprite_sheet.xml.h"
37
38 #include <limits>
39 #include <sstream>
40
41 namespace app {
42
43 using namespace ui;
44
45 namespace {
46
47 // Special key value used in default preferences to know if by default
48 // the user wants to generate texture and/or files.
49 static const char* kSpecifiedFilename = "**filename**";
50
51 struct Fit {
52 int width;
53 int height;
54 int columns;
55 int rows;
56 int freearea;
Fitapp::__anon4235e2230111::Fit57 Fit() : width(0), height(0), columns(0), rows(0), freearea(0) {
58 }
Fitapp::__anon4235e2230111::Fit59 Fit(int width, int height, int columns, int rows, int freearea) :
60 width(width), height(height), columns(columns), rows(rows), freearea(freearea) {
61 }
62 };
63
64 // Calculate best size for the given sprite
65 // TODO this function was programmed in ten minutes, please optimize it
best_fit(Sprite * sprite,int nframes,int borderPadding,int shapePadding,int innerPadding)66 Fit best_fit(Sprite* sprite, int nframes, int borderPadding, int shapePadding, int innerPadding) {
67 int framew = sprite->width()+2*innerPadding;
68 int frameh = sprite->height()+2*innerPadding;
69 Fit result(framew*nframes, frameh, nframes, 1, std::numeric_limits<int>::max());
70 int w, h;
71
72 for (w=2; w < framew; w*=2)
73 ;
74 for (h=2; h < frameh; h*=2)
75 ;
76
77 int z = 0;
78 bool fully_contained = false;
79 while (!fully_contained) { // TODO at this moment we're not
80 // getting the best fit for less
81 // freearea, just the first one.
82 gfx::Rect rgnSize(w-2*borderPadding, h-2*borderPadding);
83 gfx::Region rgn(rgnSize);
84 int contained_frames = 0;
85
86 for (int v=0; v+frameh <= rgnSize.h && !fully_contained; v+=frameh+shapePadding) {
87 for (int u=0; u+framew <= rgnSize.w; u+=framew+shapePadding) {
88 gfx::Rect framerc = gfx::Rect(u, v, framew, frameh);
89 rgn.createSubtraction(rgn, gfx::Region(framerc));
90
91 ++contained_frames;
92 if (nframes == contained_frames) {
93 fully_contained = true;
94 break;
95 }
96 }
97 }
98
99 if (fully_contained) {
100 // TODO convert this to a template function gfx::area()
101 int freearea = 0;
102 for (const gfx::Rect& rgnRect : rgn)
103 freearea += rgnRect.w * rgnRect.h;
104
105 Fit fit(w, h, (w / framew), (h / frameh), freearea);
106 if (fit.freearea < result.freearea)
107 result = fit;
108 }
109
110 if ((++z) & 1) w *= 2;
111 else h *= 2;
112 }
113
114 return result;
115 }
116
calculate_sheet_size(Sprite * sprite,int nframes,int columns,int rows,int borderPadding,int shapePadding,int innerPadding)117 Fit calculate_sheet_size(Sprite* sprite, int nframes,
118 int columns, int rows,
119 int borderPadding, int shapePadding, int innerPadding) {
120 if (columns == 0) {
121 rows = MID(1, rows, nframes);
122 columns = ((nframes/rows) + ((nframes%rows) > 0 ? 1: 0));
123 }
124 else {
125 columns = MID(1, columns, nframes);
126 rows = ((nframes/columns) + ((nframes%columns) > 0 ? 1: 0));
127 }
128
129 return Fit(
130 2*borderPadding + (sprite->width()+2*innerPadding)*columns + (columns-1)*shapePadding,
131 2*borderPadding + (sprite->height()+2*innerPadding)*rows + (rows-1)*shapePadding,
132 columns, rows, 0);
133 }
134
ask_overwrite(bool askFilename,std::string filename,bool askDataname,std::string dataname)135 bool ask_overwrite(bool askFilename, std::string filename,
136 bool askDataname, std::string dataname) {
137 if ((askFilename &&
138 !filename.empty() &&
139 base::is_file(filename)) ||
140 (askDataname &&
141 !dataname.empty() &&
142 base::is_file(dataname))) {
143 std::stringstream text;
144
145 if (base::is_file(filename))
146 text << "<<" << base::get_file_name(filename).c_str();
147
148 if (base::is_file(dataname))
149 text << "<<" << base::get_file_name(dataname).c_str();
150
151 int ret = OptionalAlert::show(
152 Preferences::instance().spriteSheet.showOverwriteFilesAlert,
153 1, // Yes is the default option when the alert dialog is disabled
154 fmt::format(Strings::alerts_overwrite_files_on_export_sprite_sheet(),
155 text.str()));
156 if (ret != 1)
157 return false;
158 }
159 return true;
160 }
161
162 }
163
164 class ExportSpriteSheetWindow : public app::gen::ExportSpriteSheet {
165 public:
ExportSpriteSheetWindow(Site & site,DocumentPreferences & docPref)166 ExportSpriteSheetWindow(Site& site, DocumentPreferences& docPref)
167 : m_site(site)
168 , m_sprite(site.sprite())
169 , m_docPref(docPref)
170 , m_filenameAskOverwrite(true)
171 , m_dataFilenameAskOverwrite(true)
172 {
173 static_assert(
174 (int)app::SpriteSheetType::None == 0 &&
175 (int)app::SpriteSheetType::Horizontal == 1 &&
176 (int)app::SpriteSheetType::Vertical == 2 &&
177 (int)app::SpriteSheetType::Rows == 3 &&
178 (int)app::SpriteSheetType::Columns == 4,
179 "SpriteSheetType enum changed");
180
181 sheetType()->addItem("Horizontal Strip");
182 sheetType()->addItem("Vertical Strip");
183 sheetType()->addItem("By Rows");
184 sheetType()->addItem("By Columns");
185 if (m_docPref.spriteSheet.type() != app::SpriteSheetType::None)
186 sheetType()->setSelectedItemIndex((int)m_docPref.spriteSheet.type()-1);
187
188 fill_layers_combobox(
189 m_sprite, layers(), m_docPref.spriteSheet.layer());
190
191 fill_frames_combobox(
192 m_sprite, frames(), m_docPref.spriteSheet.frameTag());
193
194 openGenerated()->setSelected(m_docPref.spriteSheet.openGenerated());
195 trimEnabled()->setSelected(m_docPref.spriteSheet.trim());
196
197 borderPadding()->setTextf("%d", m_docPref.spriteSheet.borderPadding());
198 shapePadding()->setTextf("%d", m_docPref.spriteSheet.shapePadding());
199 innerPadding()->setTextf("%d", m_docPref.spriteSheet.innerPadding());
200 paddingEnabled()->setSelected(
201 m_docPref.spriteSheet.borderPadding() ||
202 m_docPref.spriteSheet.shapePadding() ||
203 m_docPref.spriteSheet.innerPadding());
204 paddingContainer()->setVisible(paddingEnabled()->isSelected());
205
206 for (int i=2; i<=8192; i*=2) {
207 std::string value = base::convert_to<std::string>(i);
208 if (i >= m_sprite->width()) fitWidth()->addItem(value);
209 if (i >= m_sprite->height()) fitHeight()->addItem(value);
210 }
211
212 if (m_docPref.spriteSheet.bestFit()) {
213 bestFit()->setSelected(true);
214 onBestFit();
215 }
216 else {
217 columns()->setTextf("%d", m_docPref.spriteSheet.columns());
218 rows()->setTextf("%d", m_docPref.spriteSheet.rows());
219 onColumnsChange();
220
221 if (m_docPref.spriteSheet.width() > 0 || m_docPref.spriteSheet.height() > 0) {
222 if (m_docPref.spriteSheet.width() > 0)
223 fitWidth()->getEntryWidget()->setTextf("%d", m_docPref.spriteSheet.width());
224
225 if (m_docPref.spriteSheet.height() > 0)
226 fitHeight()->getEntryWidget()->setTextf("%d", m_docPref.spriteSheet.height());
227
228 onSizeChange();
229 }
230 }
231
232 m_filename = m_docPref.spriteSheet.textureFilename();
233 imageEnabled()->setSelected(!m_filename.empty());
234 imageFilename()->setVisible(imageEnabled()->isSelected());
235
236 m_dataFilename = m_docPref.spriteSheet.dataFilename();
237 dataEnabled()->setSelected(!m_dataFilename.empty());
238 dataFormat()->setSelectedItemIndex(int(m_docPref.spriteSheet.dataFormat()));
239 listLayers()->setSelected(m_docPref.spriteSheet.listLayers());
240 listTags()->setSelected(m_docPref.spriteSheet.listFrameTags());
241 listSlices()->setSelected(m_docPref.spriteSheet.listSlices());
242 updateDataFields();
243
244 std::string base = site.document()->filename();
245 base = base::join_path(base::get_file_path(base), base::get_file_title(base));
246
247 if (m_filename.empty() ||
248 m_filename == kSpecifiedFilename) {
249 std::string defExt = Preferences::instance().spriteSheet.defaultExtension();
250
251 if (base::utf8_icmp(base::get_file_extension(site.document()->filename()), defExt) == 0)
252 m_filename = base + "-sheet." + defExt;
253 else
254 m_filename = base + "." + defExt;
255 }
256
257 if (m_dataFilename.empty() ||
258 m_dataFilename == kSpecifiedFilename)
259 m_dataFilename = base + ".json";
260
261 exportButton()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onExport, this));
262 sheetType()->Change.connect(&ExportSpriteSheetWindow::onSheetTypeChange, this);
263 columns()->Change.connect(base::Bind<void>(&ExportSpriteSheetWindow::onColumnsChange, this));
264 rows()->Change.connect(base::Bind<void>(&ExportSpriteSheetWindow::onRowsChange, this));
265 fitWidth()->Change.connect(base::Bind<void>(&ExportSpriteSheetWindow::onSizeChange, this));
266 fitHeight()->Change.connect(base::Bind<void>(&ExportSpriteSheetWindow::onSizeChange, this));
267 bestFit()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onBestFit, this));
268 borderPadding()->Change.connect(base::Bind<void>(&ExportSpriteSheetWindow::onPaddingChange, this));
269 shapePadding()->Change.connect(base::Bind<void>(&ExportSpriteSheetWindow::onPaddingChange, this));
270 innerPadding()->Change.connect(base::Bind<void>(&ExportSpriteSheetWindow::onPaddingChange, this));
271 imageEnabled()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onImageEnabledChange, this));
272 imageFilename()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onImageFilename, this));
273 dataEnabled()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onDataEnabledChange, this));
274 dataFilename()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onDataFilename, this));
275 paddingEnabled()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onPaddingEnabledChange, this));
276 frames()->Change.connect(base::Bind<void>(&ExportSpriteSheetWindow::onFramesChange, this));
277 openGenerated()->Click.connect(base::Bind<void>(&ExportSpriteSheetWindow::onOpenGeneratedChange, this));
278
279 onSheetTypeChange();
280 onFileNamesChange();
281 updateExportButton();
282 }
283
ok() const284 bool ok() const {
285 return closer() == exportButton();
286 }
287
spriteSheetTypeValue() const288 app::SpriteSheetType spriteSheetTypeValue() const {
289 return (app::SpriteSheetType)(sheetType()->getSelectedItemIndex()+1);
290 }
291
columnsValue() const292 int columnsValue() const {
293 if (spriteSheetTypeValue() != SpriteSheetType::Columns)
294 return columns()->textInt();
295 else
296 return 0;
297 }
298
rowsValue() const299 int rowsValue() const {
300 if (spriteSheetTypeValue() == SpriteSheetType::Columns)
301 return rows()->textInt();
302 else
303 return 0;
304 }
305
fitWidthValue() const306 int fitWidthValue() const {
307 return fitWidth()->getEntryWidget()->textInt();
308 }
309
fitHeightValue() const310 int fitHeightValue() const {
311 return fitHeight()->getEntryWidget()->textInt();
312 }
313
bestFitValue() const314 bool bestFitValue() const {
315 return bestFit()->isSelected();
316 }
317
filenameValue() const318 std::string filenameValue() const {
319 if (imageEnabled()->isSelected())
320 return m_filename;
321 else
322 return std::string();
323 }
324
dataFilenameValue() const325 std::string dataFilenameValue() const {
326 if (dataEnabled()->isSelected())
327 return m_dataFilename;
328 else
329 return std::string();
330 }
331
dataFormatValue() const332 DocExporter::DataFormat dataFormatValue() const {
333 if (dataEnabled()->isSelected())
334 return DocExporter::DataFormat(dataFormat()->getSelectedItemIndex());
335 else
336 return DocExporter::DefaultDataFormat;
337 }
338
borderPaddingValue() const339 int borderPaddingValue() const {
340 if (paddingEnabled()->isSelected()) {
341 int value = borderPadding()->textInt();
342 return MID(0, value, 100);
343 }
344 else
345 return 0;
346 }
347
shapePaddingValue() const348 int shapePaddingValue() const {
349 if (paddingEnabled()->isSelected()) {
350 int value = shapePadding()->textInt();
351 return MID(0, value, 100);
352 }
353 else
354 return 0;
355 }
356
innerPaddingValue() const357 int innerPaddingValue() const {
358 if (paddingEnabled()->isSelected()) {
359 int value = innerPadding()->textInt();
360 return MID(0, value, 100);
361 }
362 else
363 return 0;
364 }
365
trimValue() const366 bool trimValue() const {
367 return trimEnabled()->isSelected();
368 }
369
openGeneratedValue() const370 bool openGeneratedValue() const {
371 return openGenerated()->isSelected();
372 }
373
layerValue() const374 std::string layerValue() const {
375 return layers()->getValue();
376 }
377
frameTagValue() const378 std::string frameTagValue() const {
379 return frames()->getValue();
380 }
381
listLayersValue() const382 bool listLayersValue() const {
383 return listLayers()->isSelected();
384 }
385
listFrameTagsValue() const386 bool listFrameTagsValue() const {
387 return listTags()->isSelected();
388 }
389
listSlicesValue() const390 bool listSlicesValue() const {
391 return listSlices()->isSelected();
392 }
393
394 private:
395
onExport()396 void onExport() {
397 if (!ask_overwrite(m_filenameAskOverwrite, filenameValue(),
398 m_dataFilenameAskOverwrite, dataFilenameValue()))
399 return;
400
401 closeWindow(exportButton());
402 }
403
onSheetTypeChange()404 void onSheetTypeChange() {
405 bool rowsState = false;
406 bool colsState = false;
407 bool matrixState = false;
408 switch (spriteSheetTypeValue()) {
409 case app::SpriteSheetType::Rows:
410 colsState = true;
411 matrixState = true;
412 break;
413 case app::SpriteSheetType::Columns:
414 rowsState = true;
415 matrixState = true;
416 break;
417 }
418
419 columnsLabel()->setVisible(colsState);
420 columns()->setVisible(colsState);
421
422 rowsLabel()->setVisible(rowsState);
423 rows()->setVisible(rowsState);
424
425 fitWidthLabel()->setVisible(matrixState);
426 fitWidth()->setVisible(matrixState);
427 fitHeightLabel()->setVisible(matrixState);
428 fitHeight()->setVisible(matrixState);
429 bestFitFiller()->setVisible(matrixState);
430 bestFit()->setVisible(matrixState);
431
432 resize();
433 }
434
onFileNamesChange()435 void onFileNamesChange() {
436 imageFilename()->setText("Select File: " + base::get_file_name(m_filename));
437 dataFilename()->setText("Select File: " + base::get_file_name(m_dataFilename));
438 resize();
439 }
440
onColumnsChange()441 void onColumnsChange() {
442 bestFit()->setSelected(false);
443 updateSizeFields();
444 }
445
onRowsChange()446 void onRowsChange() {
447 bestFit()->setSelected(false);
448 updateSizeFields();
449 }
450
onSizeChange()451 void onSizeChange() {
452 columns()->setTextf("%d", fitWidthValue() / m_sprite->width());
453 rows()->setTextf("%d", fitHeightValue() / m_sprite->height());
454 bestFit()->setSelected(false);
455 }
456
onBestFit()457 void onBestFit() {
458 updateSizeFields();
459 }
460
onImageFilename()461 void onImageFilename() {
462 base::paths newFilename;
463 if (!app::show_file_selector(
464 "Save Sprite Sheet", m_filename,
465 get_writable_extensions(),
466 FileSelectorType::Save, newFilename))
467 return;
468
469 ASSERT(!newFilename.empty());
470
471 m_filename = newFilename.front();
472 m_filenameAskOverwrite = false; // Already asked in file selector
473 onFileNamesChange();
474 }
475
onImageEnabledChange()476 void onImageEnabledChange() {
477 m_filenameAskOverwrite = true;
478
479 imageFilename()->setVisible(imageEnabled()->isSelected());
480 updateExportButton();
481 resize();
482 }
483
onDataFilename()484 void onDataFilename() {
485 // TODO hardcoded "json" extension
486 base::paths exts = { "json" };
487 base::paths newFilename;
488 if (!app::show_file_selector(
489 "Save JSON Data", m_dataFilename, exts,
490 FileSelectorType::Save, newFilename))
491 return;
492
493 ASSERT(!newFilename.empty());
494
495 m_dataFilename = newFilename.front();
496 m_dataFilenameAskOverwrite = false; // Already asked in file selector
497 onFileNamesChange();
498 }
499
onDataEnabledChange()500 void onDataEnabledChange() {
501 m_dataFilenameAskOverwrite = true;
502
503 updateDataFields();
504 updateExportButton();
505 resize();
506 }
507
onPaddingEnabledChange()508 void onPaddingEnabledChange() {
509 paddingContainer()->setVisible(paddingEnabled()->isSelected());
510 resize();
511 updateSizeFields();
512 }
513
onPaddingChange()514 void onPaddingChange() {
515 updateSizeFields();
516 }
517
onFramesChange()518 void onFramesChange() {
519 updateSizeFields();
520 }
521
onOpenGeneratedChange()522 void onOpenGeneratedChange() {
523 updateExportButton();
524 }
525
resize()526 void resize() {
527 gfx::Size reqSize = sizeHint();
528 moveWindow(gfx::Rect(origin(), reqSize));
529 layout();
530 invalidate();
531 }
532
updateExportButton()533 void updateExportButton() {
534 exportButton()->setEnabled(
535 imageEnabled()->isSelected() ||
536 dataEnabled()->isSelected() ||
537 openGenerated()->isSelected());
538 }
539
updateSizeFields()540 void updateSizeFields() {
541 SelectedFrames selFrames;
542 calculate_selected_frames(m_site,
543 frameTagValue(),
544 selFrames);
545
546 frame_t nframes = selFrames.size();
547
548 Fit fit;
549 if (bestFit()->isSelected()) {
550 fit = best_fit(m_sprite, nframes,
551 borderPaddingValue(), shapePaddingValue(), innerPaddingValue());
552 }
553 else {
554 fit = calculate_sheet_size(
555 m_sprite, nframes,
556 columnsValue(),
557 rowsValue(),
558 borderPaddingValue(),
559 shapePaddingValue(),
560 innerPaddingValue());
561 }
562
563 columns()->setTextf("%d", fit.columns);
564 rows()->setTextf("%d", fit.rows);
565 fitWidth()->getEntryWidget()->setTextf("%d", fit.width);
566 fitHeight()->getEntryWidget()->setTextf("%d", fit.height);
567 }
568
updateDataFields()569 void updateDataFields() {
570 bool state = dataEnabled()->isSelected();
571 dataFilename()->setVisible(state);
572 dataMeta()->setVisible(state);
573 }
574
575 Site& m_site;
576 Sprite* m_sprite;
577 DocumentPreferences& m_docPref;
578 std::string m_filename;
579 std::string m_dataFilename;
580 bool m_filenameAskOverwrite;
581 bool m_dataFilenameAskOverwrite;
582 };
583
584 class ExportSpriteSheetCommand : public Command {
585 public:
586 ExportSpriteSheetCommand();
clone() const587 Command* clone() const override { return new ExportSpriteSheetCommand(*this); }
588
setUseUI(bool useUI)589 void setUseUI(bool useUI) { m_useUI = useUI; }
590
591 protected:
592 void onLoadParams(const Params& params) override;
593 bool onEnabled(Context* context) override;
594 void onExecute(Context* context) override;
595
596 private:
597 bool m_useUI;
598 bool m_askOverwrite;
599 };
600
ExportSpriteSheetCommand()601 ExportSpriteSheetCommand::ExportSpriteSheetCommand()
602 : Command(CommandId::ExportSpriteSheet(), CmdRecordableFlag)
603 , m_useUI(true)
604 , m_askOverwrite(true)
605 {
606 }
607
onLoadParams(const Params & params)608 void ExportSpriteSheetCommand::onLoadParams(const Params& params)
609 {
610 if (params.has_param("ui"))
611 m_useUI = params.get_as<bool>("ui");
612 else
613 m_useUI = true;
614
615 if (params.has_param("ask-overwrite"))
616 m_askOverwrite = params.get_as<bool>("ask-overwrite");
617 else
618 m_askOverwrite = true;
619 }
620
onEnabled(Context * context)621 bool ExportSpriteSheetCommand::onEnabled(Context* context)
622 {
623 return context->checkFlags(ContextFlags::ActiveDocumentIsWritable);
624 }
625
onExecute(Context * context)626 void ExportSpriteSheetCommand::onExecute(Context* context)
627 {
628 Site site = context->activeSite();
629 Doc* document = site.document();
630 Sprite* sprite = site.sprite();
631 DocumentPreferences& docPref(Preferences::instance().document(document));
632 bool askOverwrite = m_askOverwrite;
633
634 if (m_useUI && context->isUIAvailable()) {
635 ExportSpriteSheetWindow window(site, docPref);
636 window.openWindowInForeground();
637 if (!window.ok())
638 return;
639
640 docPref.spriteSheet.defined(true);
641 docPref.spriteSheet.type(window.spriteSheetTypeValue());
642 docPref.spriteSheet.columns(window.columnsValue());
643 docPref.spriteSheet.rows(window.rowsValue());
644 docPref.spriteSheet.width(window.fitWidthValue());
645 docPref.spriteSheet.height(window.fitHeightValue());
646 docPref.spriteSheet.bestFit(window.bestFitValue());
647 docPref.spriteSheet.textureFilename(window.filenameValue());
648 docPref.spriteSheet.dataFilename(window.dataFilenameValue());
649 docPref.spriteSheet.dataFormat(window.dataFormatValue());
650 docPref.spriteSheet.borderPadding(window.borderPaddingValue());
651 docPref.spriteSheet.shapePadding(window.shapePaddingValue());
652 docPref.spriteSheet.innerPadding(window.innerPaddingValue());
653 docPref.spriteSheet.trim(window.trimValue());
654 docPref.spriteSheet.openGenerated(window.openGeneratedValue());
655 docPref.spriteSheet.layer(window.layerValue());
656 docPref.spriteSheet.frameTag(window.frameTagValue());
657 docPref.spriteSheet.listLayers(window.listLayersValue());
658 docPref.spriteSheet.listFrameTags(window.listFrameTagsValue());
659 docPref.spriteSheet.listSlices(window.listSlicesValue());
660
661 // Default preferences for future sprites
662 DocumentPreferences& defPref(Preferences::instance().document(nullptr));
663 defPref.spriteSheet = docPref.spriteSheet;
664 defPref.spriteSheet.defined(false);
665 if (!defPref.spriteSheet.textureFilename().empty())
666 defPref.spriteSheet.textureFilename.setValueAndDefault(kSpecifiedFilename);
667 if (!defPref.spriteSheet.dataFilename().empty())
668 defPref.spriteSheet.dataFilename.setValueAndDefault(kSpecifiedFilename);
669 defPref.save();
670
671 askOverwrite = false; // Already asked in the ExportSpriteSheetWindow
672 }
673
674 app::SpriteSheetType type = docPref.spriteSheet.type();
675 int columns = docPref.spriteSheet.columns();
676 int rows = docPref.spriteSheet.rows();
677 int width = docPref.spriteSheet.width();
678 int height = docPref.spriteSheet.height();
679 bool bestFit = docPref.spriteSheet.bestFit();
680 std::string filename = docPref.spriteSheet.textureFilename();
681 std::string dataFilename = docPref.spriteSheet.dataFilename();
682 DocExporter::DataFormat dataFormat = docPref.spriteSheet.dataFormat();
683 std::string layerName = docPref.spriteSheet.layer();
684 std::string frameTagName = docPref.spriteSheet.frameTag();
685 int borderPadding = docPref.spriteSheet.borderPadding();
686 int shapePadding = docPref.spriteSheet.shapePadding();
687 int innerPadding = docPref.spriteSheet.innerPadding();
688 borderPadding = MID(0, borderPadding, 100);
689 shapePadding = MID(0, shapePadding, 100);
690 innerPadding = MID(0, innerPadding, 100);
691 const bool trimCels = docPref.spriteSheet.trim();
692 const bool listLayers = docPref.spriteSheet.listLayers();
693 const bool listFrameTags = docPref.spriteSheet.listFrameTags();
694 const bool listSlices = docPref.spriteSheet.listSlices();
695
696 if (context->isUIAvailable() && askOverwrite) {
697 if (!ask_overwrite(true, filename,
698 true, dataFilename))
699 return; // Do not overwrite
700 }
701
702 SelectedFrames selFrames;
703 FrameTag* frameTag =
704 calculate_selected_frames(site, frameTagName, selFrames);
705
706 frame_t nframes = selFrames.size();
707 ASSERT(nframes > 0);
708
709 // If the user choose to render selected layers only, we can
710 // temporaly make them visible and hide the other ones.
711 RestoreVisibleLayers layersVisibility;
712 calculate_visible_layers(site, layerName, layersVisibility);
713
714 SelectedLayers selLayers;
715 if (layerName != kSelectedLayers) {
716 // TODO add a getLayerByName
717 for (Layer* layer : sprite->allLayers()) {
718 if (layer->name() == layerName) {
719 selLayers.insert(layer);
720 break;
721 }
722 }
723 }
724
725 if (bestFit) {
726 Fit fit = best_fit(sprite, nframes, borderPadding, shapePadding, innerPadding);
727 columns = fit.columns;
728 rows = fit.rows;
729 width = fit.width;
730 height = fit.height;
731 }
732
733 int sheet_w = 0;
734 int sheet_h = 0;
735
736 switch (type) {
737 case app::SpriteSheetType::Horizontal:
738 columns = sprite->totalFrames();
739 rows = 1;
740 break;
741 case app::SpriteSheetType::Vertical:
742 columns = 1;
743 rows = nframes;
744 break;
745 case app::SpriteSheetType::Rows:
746 case app::SpriteSheetType::Columns:
747 if (width > 0) sheet_w = width;
748 if (height > 0) sheet_h = height;
749 break;
750 }
751
752 Fit fit = calculate_sheet_size(
753 sprite, nframes,
754 columns, rows,
755 borderPadding, shapePadding, innerPadding);
756 if (sheet_w == 0) sheet_w = fit.width;
757 if (sheet_h == 0) sheet_h = fit.height;
758
759 DocExporter exporter;
760 if (!filename.empty())
761 exporter.setTextureFilename(filename);
762 if (!dataFilename.empty()) {
763 exporter.setDataFilename(dataFilename);
764 exporter.setDataFormat(dataFormat);
765 }
766 exporter.setTextureWidth(sheet_w);
767 exporter.setTextureHeight(sheet_h);
768 exporter.setSpriteSheetType(type);
769 exporter.setBorderPadding(borderPadding);
770 exporter.setShapePadding(shapePadding);
771 exporter.setInnerPadding(innerPadding);
772 exporter.setTrimCels(trimCels);
773 if (listLayers) exporter.setListLayers(true);
774 if (listFrameTags) exporter.setListFrameTags(true);
775 if (listSlices) exporter.setListSlices(true);
776 exporter.addDocument(document, frameTag,
777 (!selLayers.empty() ? &selLayers: nullptr),
778 (!selFrames.empty() ? &selFrames: nullptr));
779
780 base::UniquePtr<Doc> newDocument(exporter.exportSheet(context));
781 if (!newDocument)
782 return;
783
784 StatusBar* statusbar = StatusBar::instance();
785 if (statusbar)
786 statusbar->showTip(1000, "Sprite Sheet Generated");
787
788 // Copy background and grid preferences
789 {
790 DocumentPreferences& newDocPref(Preferences::instance().document(newDocument));
791 newDocPref.bg = docPref.bg;
792 newDocPref.grid = docPref.grid;
793 newDocPref.pixelGrid = docPref.pixelGrid;
794 Preferences::instance().removeDocument(newDocument);
795 }
796
797 if (docPref.spriteSheet.openGenerated()) {
798 // Setup a filename for the new document in case that user didn't
799 // save the file/specified one output filename.
800 if (filename.empty()) {
801 std::string fn = document->filename();
802 std::string ext = base::get_file_extension(fn);
803 if (!ext.empty())
804 ext.insert(0, 1, '.');
805
806 newDocument->setFilename(
807 base::join_path(base::get_file_path(fn),
808 base::get_file_title(fn) + "-Sheet") + ext);
809 }
810
811 newDocument->setContext(context);
812 newDocument.release();
813 }
814 }
815
createExportSpriteSheetCommand()816 Command* CommandFactory::createExportSpriteSheetCommand()
817 {
818 return new ExportSpriteSheetCommand;
819 }
820
821 } // namespace app
822