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