diff --git a/src/css/settings-export.css b/src/css/settings-export.css
index 1dbf14e2..593584b1 100644
--- a/src/css/settings-export.css
+++ b/src/css/settings-export.css
@@ -151,7 +151,7 @@
border-radius: 2px;
}
-.export-panel-gif .export-panel-section {
+.export-panel-row {
display: flex;
align-items: center;
overflow: hidden;
@@ -162,3 +162,19 @@
width: 75px;
flex-shrink: 0;
}
+
+.export-panel-png .textfield {
+ width: 50px;
+}
+
+.png-export-dimension-info {
+ font-style: italic;
+ font-weight: normal;
+ margin-left: 5px;
+}
+
+#png-export-columns {
+ /* Override default textfield padding-right to keep the number spinners
+ aligned to the right. */
+ padding-right: 0;
+}
diff --git a/src/js/Events.js b/src/js/Events.js
index c00f29c8..dc2b9e16 100644
--- a/src/js/Events.js
+++ b/src/js/Events.js
@@ -72,6 +72,7 @@ var Events = {
HIDE_PROGRESS: 'HIDE_PROGRESS',
ZOOM_CHANGED : 'ZOOM_CHANGED',
+ EXPORT_SCALE_CHANGED : 'EXPORT_SCALE_CHANGED',
CURRENT_COLORS_UPDATED : 'CURRENT_COLORS_UPDATED',
diff --git a/src/js/controller/settings/exportimage/ExportController.js b/src/js/controller/settings/exportimage/ExportController.js
index 68b282c9..32342cd6 100644
--- a/src/js/controller/settings/exportimage/ExportController.js
+++ b/src/js/controller/settings/exportimage/ExportController.js
@@ -35,7 +35,7 @@
this.widthInput = document.querySelector('.export-resize .resize-width');
this.heightInput = document.querySelector('.export-resize .resize-height');
- var scale = pskl.UserSettings.get(pskl.UserSettings.EXPORT_SCALING);
+ var scale = pskl.UserSettings.get(pskl.UserSettings.EXPORT_SCALE);
this.sizeInputWidget = new pskl.widgets.SizeInput({
widthInput : this.widthInput,
heightInput : this.heightInput,
@@ -94,7 +94,7 @@
if (Math.round(this.getExportZoom()) != value) {
this.sizeInputWidget.setWidth(this.piskelController.getWidth() * value);
}
- pskl.UserSettings.set(pskl.UserSettings.EXPORT_SCALING, value);
+ pskl.UserSettings.set(pskl.UserSettings.EXPORT_SCALE, value);
}
};
@@ -111,6 +111,7 @@
}
this.updateScaleText_(zoom);
+ $.publish(Events.EXPORT_SCALE_CHANGED);
this.scaleInput.value = Math.round(zoom);
if (zoom >= 1 && zoom <= 32) {
diff --git a/src/js/controller/settings/exportimage/GifExportController.js b/src/js/controller/settings/exportimage/GifExportController.js
index eb6be504..75088aee 100644
--- a/src/js/controller/settings/exportimage/GifExportController.js
+++ b/src/js/controller/settings/exportimage/GifExportController.js
@@ -24,10 +24,6 @@
this.addEventListener(this.downloadButton, 'click', this.onDownloadButtonClick_);
};
- ns.GifExportController.prototype.destroy = function () {
- this.superclass.destroy.call(this);
- };
-
ns.GifExportController.prototype.getZoom_ = function () {
return this.exportController.getExportZoom();
};
diff --git a/src/js/controller/settings/exportimage/MiscExportController.js b/src/js/controller/settings/exportimage/MiscExportController.js
index 1c299a2c..f639d51f 100644
--- a/src/js/controller/settings/exportimage/MiscExportController.js
+++ b/src/js/controller/settings/exportimage/MiscExportController.js
@@ -10,12 +10,11 @@
pskl.utils.inherit(ns.MiscExportController, pskl.controller.settings.AbstractSettingController);
ns.MiscExportController.prototype.init = function () {
-
- var downloadButton = document.querySelector('.c-download-button');
- this.addEventListener(downloadButton, 'click', this.onCDownloadButtonClick_);
+ var cDownloadButton = document.querySelector('.c-download-button');
+ this.addEventListener(cDownloadButton, 'click', this.onDownloadCFileClick_);
};
- ns.MiscExportController.prototype.onCDownloadButtonClick_ = function (evt) {
+ ns.MiscExportController.prototype.onDownloadCFileClick_ = function (evt) {
var fileName = this.getPiskelName_() + '.c';
var cName = this.getPiskelName_().replace(' ','_');
var width = this.piskelController.getWidth();
diff --git a/src/js/controller/settings/exportimage/PngExportController.js b/src/js/controller/settings/exportimage/PngExportController.js
index cf1944eb..838426be 100644
--- a/src/js/controller/settings/exportimage/PngExportController.js
+++ b/src/js/controller/settings/exportimage/PngExportController.js
@@ -1,41 +1,149 @@
(function () {
var ns = $.namespace('pskl.controller.settings.exportimage');
+ var dimensionInfoPattern = '{{width}} x {{height}} px, {{frames}}
{{rows}}, {{columns}}.';
+
+ // Shortcut to pskl.utils.Template.replace
+ var replace = pskl.utils.Template.replace;
+
+ // Helper to return "X items" or "1 item" if X is 1.
+ var pluralize = function (word, count) {
+ if (count === 1) {
+ return '1 ' + word;
+ }
+ return count + ' ' + word + 's';
+ };
+
+ // Compute the nearest power of two for the provided number.
+ var getNearestPowerOfTwo = function (number) {
+ return Math.pow(2, Math.ceil(Math.log(number) / Math.log(2)));
+ };
+
ns.PngExportController = function (piskelController, exportController) {
this.piskelController = piskelController;
this.exportController = exportController;
+ this.onScaleChanged_ = this.onScaleChanged_.bind(this);
};
pskl.utils.inherit(ns.PngExportController, pskl.controller.settings.AbstractSettingController);
ns.PngExportController.prototype.init = function () {
+ this.layoutContainer = document.querySelector('.png-export-layout-section');
+ this.dimensionInfo = document.querySelector('.png-export-dimension-info');
+ this.columnsInput = document.querySelector('#png-export-columns');
+ this.powerTwo = document.querySelector('#png-export-power-two');
var downloadButton = document.querySelector('.png-download-button');
- this.addEventListener(downloadButton, 'click', this.onPngDownloadButtonClick_);
+
+ this.initLayoutSection_();
+ this.updateDimensionLabel_();
+
+ this.addEventListener(downloadButton, 'click', this.onDownloadClick_);
+ this.addEventListener(this.columnsInput, 'input', this.onColumnsChanged_);
+ this.addEventListener(this.powerTwo, 'change', this.onPowerTwoChanged_);
+ $.subscribe(Events.EXPORT_SCALE_CHANGED, this.onScaleChanged_);
};
- ns.PngExportController.prototype.onPngDownloadButtonClick_ = function (evt) {
- var fileName = this.getPiskelName_() + '.png';
+ ns.PngExportController.prototype.destroy = function () {
+ $.unsubscribe(Events.EXPORT_SCALE_CHANGED, this.onScaleChanged_);
+ this.superclass.destroy.call(this);
+ };
- var outputCanvas = this.getFramesheetAsCanvas();
+ /**
+ * Initalize all controls related to the spritesheet layout.
+ */
+ ns.PngExportController.prototype.initLayoutSection_ = function () {
+ var frames = this.piskelController.getFrameCount();
+ if (frames === 1) {
+ // Hide the layout section if only one frame is defined.
+ this.layoutContainer.style.display = 'none';
+ } else {
+ this.columnsInput.value = this.getBestFit_();
+ this.powerTwo.checked = pskl.UserSettings.get('EXPORT_PNG_POWER_TWO');
+ }
+ };
+
+ ns.PngExportController.prototype.updateDimensionLabel_ = function () {
+ var zoom = this.exportController.getExportZoom();
+ var frames = this.piskelController.getFrameCount();
+ var width = this.piskelController.getWidth() * zoom;
+ var height = this.piskelController.getHeight() * zoom;
+
+ var columns = this.getColumns_();
+ var rows = Math.ceil(frames / columns);
+ width = columns * width;
+ height = rows * height;
+
+ if (this.isPowerTwoEnabled_()) {
+ width = getNearestPowerOfTwo(width);
+ height = getNearestPowerOfTwo(height);
+ }
+
+ this.dimensionInfo.innerHTML = replace(dimensionInfoPattern, {
+ width: width,
+ height: height,
+ rows: pluralize('row', rows),
+ columns: pluralize('column', columns),
+ frames: pluralize('frame', frames),
+ });
+ };
+
+ ns.PngExportController.prototype.getColumns_ = function () {
+ return parseInt(this.columnsInput.value || 1, 10);
+ };
+
+ ns.PngExportController.prototype.getBestFit_ = function () {
+ var ratio = this.piskelController.getWidth() / this.piskelController.getHeight();
+ var frameCount = this.piskelController.getFrameCount();
+ var bestFit = Math.round(Math.sqrt(frameCount / ratio));
+
+ return Math.max(1, Math.min(bestFit, frameCount));
+ };
+
+ ns.PngExportController.prototype.isPowerTwoEnabled_ = function () {
+ return pskl.UserSettings.get('EXPORT_PNG_POWER_TWO');
+ };
+
+ ns.PngExportController.prototype.onScaleChanged_ = function () {
+ this.updateDimensionLabel_();
+ };
+
+ ns.PngExportController.prototype.onColumnsChanged_ = function () {
+ if (this.getColumns_() > this.piskelController.getFrameCount()) {
+ this.columnsInput.value = this.piskelController.getFrameCount();
+ } else if (this.getColumns_() < 1) {
+ this.columnsInput.value = 1;
+ }
+ this.updateDimensionLabel_();
+ };
+
+ ns.PngExportController.prototype.onPowerTwoChanged_ = function () {
+ pskl.UserSettings.set('EXPORT_PNG_POWER_TWO', this.powerTwo.checked);
+ this.updateDimensionLabel_();
+ };
+
+ ns.PngExportController.prototype.onDownloadClick_ = function (evt) {
+ var name = this.piskelController.getPiskel().getDescriptor().name;
+ var fileName = name + '.png';
+
+ var renderer = new pskl.rendering.PiskelRenderer(this.piskelController);
+ var outputCanvas = renderer.renderAsCanvas(this.getColumns_());
+ var width = outputCanvas.width;
+ var height = outputCanvas.height;
var zoom = this.exportController.getExportZoom();
if (zoom != 1) {
- var width = outputCanvas.width * zoom;
- var height = outputCanvas.height * zoom;
- outputCanvas = pskl.utils.ImageResizer.resize(outputCanvas, width, height, false);
+ outputCanvas = pskl.utils.ImageResizer.resize(outputCanvas, width * zoom, height * zoom, false);
+ }
+
+ if (this.isPowerTwoEnabled_()) {
+ var paddingCanvas = pskl.utils.CanvasUtils.createCanvas(
+ getNearestPowerOfTwo(width * zoom), getNearestPowerOfTwo(height * zoom));
+ paddingCanvas.getContext('2d').drawImage(outputCanvas, 0, 0);
+ outputCanvas = paddingCanvas;
}
pskl.utils.BlobUtils.canvasToBlob(outputCanvas, function(blob) {
pskl.utils.FileUtils.downloadAsFile(blob, fileName);
});
};
-
- ns.PngExportController.prototype.getPiskelName_ = function () {
- return this.piskelController.getPiskel().getDescriptor().name;
- };
-
- ns.PngExportController.prototype.getFramesheetAsCanvas = function () {
- var renderer = new pskl.rendering.PiskelRenderer(this.piskelController);
- return renderer.renderAsCanvas();
- };
})();
diff --git a/src/js/rendering/FramesheetRenderer.js b/src/js/rendering/FramesheetRenderer.js
index d0694eaa..2e325e17 100644
--- a/src/js/rendering/FramesheetRenderer.js
+++ b/src/js/rendering/FramesheetRenderer.js
@@ -13,11 +13,17 @@
}
};
- ns.FramesheetRenderer.prototype.renderAsCanvas = function () {
- var canvas = this.createCanvas_();
+ ns.FramesheetRenderer.prototype.renderAsCanvas = function (columns) {
+ columns = columns || this.frames.length;
+ var rows = Math.ceil(this.frames.length / columns);
+
+ var canvas = this.createCanvas_(columns, rows);
+
for (var i = 0 ; i < this.frames.length ; i++) {
var frame = this.frames[i];
- this.drawFrameInCanvas_(frame, canvas, i * frame.getWidth(), 0);
+ var posX = (i % columns) * frame.getWidth();
+ var posY = Math.floor(i / columns) * frame.getHeight();
+ this.drawFrameInCanvas_(frame, canvas, posX, posY);
}
return canvas;
};
@@ -32,11 +38,10 @@
});
};
- ns.FramesheetRenderer.prototype.createCanvas_ = function () {
+ ns.FramesheetRenderer.prototype.createCanvas_ = function (columns, rows) {
var sampleFrame = this.frames[0];
- var count = this.frames.length;
- var width = count * sampleFrame.getWidth();
- var height = sampleFrame.getHeight();
+ var width = columns * sampleFrame.getWidth();
+ var height = rows * sampleFrame.getHeight();
return pskl.utils.CanvasUtils.createCanvas(width, height);
};
})();
diff --git a/src/js/rendering/PiskelRenderer.js b/src/js/rendering/PiskelRenderer.js
index 1cbfbe3c..79206c3c 100644
--- a/src/js/rendering/PiskelRenderer.js
+++ b/src/js/rendering/PiskelRenderer.js
@@ -11,11 +11,17 @@
this.frames = frames;
};
- ns.PiskelRenderer.prototype.renderAsCanvas = function () {
- var canvas = this.createCanvas_();
+ ns.PiskelRenderer.prototype.renderAsCanvas = function (columns) {
+ columns = columns || this.frames.length;
+ var rows = Math.ceil(this.frames.length / columns);
+
+ var canvas = this.createCanvas_(columns, rows);
+
for (var i = 0 ; i < this.frames.length ; i++) {
var frame = this.frames[i];
- this.drawFrameInCanvas_(frame, canvas, i * this.piskelController.getWidth(), 0);
+ var posX = (i % columns) * this.piskelController.getWidth();
+ var posY = Math.floor(i / columns) * this.piskelController.getHeight();
+ this.drawFrameInCanvas_(frame, canvas, posX, posY);
}
return canvas;
};
@@ -25,10 +31,9 @@
context.drawImage(frame, offsetWidth, offsetHeight, frame.width, frame.height);
};
- ns.PiskelRenderer.prototype.createCanvas_ = function () {
- var count = this.frames.length;
- var width = count * this.piskelController.getWidth();
- var height = this.piskelController.getHeight();
+ ns.PiskelRenderer.prototype.createCanvas_ = function (columns, rows) {
+ var width = columns * this.piskelController.getWidth();
+ var height = rows * this.piskelController.getHeight();
return pskl.utils.CanvasUtils.createCanvas(width, height);
};
})();
diff --git a/src/js/utils/UserSettings.js b/src/js/utils/UserSettings.js
index bc056cf1..b05c495e 100644
--- a/src/js/utils/UserSettings.js
+++ b/src/js/utils/UserSettings.js
@@ -12,7 +12,8 @@
ONION_SKIN : 'ONION_SKIN',
LAYER_PREVIEW : 'LAYER_PREVIEW',
LAYER_OPACITY : 'LAYER_OPACITY',
- EXPORT_SCALING: 'EXPORT_SCALING',
+ EXPORT_PNG_POWER_TWO: 'EXPORT_PNG_POWER_TWO',
+ EXPORT_SCALE: 'EXPORT_SCALE',
EXPORT_TAB: 'EXPORT_TAB',
PEN_SIZE : 'PEN_SIZE',
RESIZE_SETTINGS: 'RESIZE_SETTINGS',
@@ -30,8 +31,9 @@
'ONION_SKIN' : false,
'LAYER_OPACITY' : 0.2,
'LAYER_PREVIEW' : true,
- 'EXPORT_SCALING' : 1,
- 'EXPORT_TAB' : 'png',
+ 'EXPORT_PNG_POWER_TWO' : false,
+ 'EXPORT_SCALE' : 1,
+ 'EXPORT_TAB' : 'gif',
'PEN_SIZE' : 1,
'RESIZE_SETTINGS': {
maintainRatio : true,
diff --git a/src/templates/settings/export.html b/src/templates/settings/export.html
index 5feea30e..d16ea0da 100644
--- a/src/templates/settings/export.html
+++ b/src/templates/settings/export.html
@@ -15,8 +15,8 @@