Issue #293: Add spritesheet layout options in PNG export tab

This commit is contained in:
Julian Descottes 2016-05-29 10:45:19 +02:00
parent 5a0d5da5f4
commit 169ce21556
11 changed files with 198 additions and 45 deletions

View File

@ -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,13 @@
width: 75px;
flex-shrink: 0;
}
.export-panel-png .textfield {
width: 50px;
}
.png-export-dimension-info {
font-style: italic;
font-weight: normal;
margin-left: 5px;
}

View File

@ -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',

View File

@ -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) {

View File

@ -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();
};

View File

@ -1,41 +1,158 @@
(function () {
var ns = $.namespace('pskl.controller.settings.exportimage');
var dimensionInfoPattern = '{{width}} x {{height}} px, {{frames}}<br/>{{rows}}, {{columns}}.';
var replace = pskl.utils.Template.replace;
var pluralize = function (word, count) {
if (count === 1) {
return '1 ' + word;
}
return count + ' ' + word + 's';
};
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, 'keydown', this.onColumnsKeydown_);
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.onColumnsKeydown_ = function (evt) {
var key = pskl.service.keyboard.KeycodeTranslator.toChar(evt.keyCode);
if (key === 'up') {
evt.preventDefault();
this.columnsInput.value = this.getColumns_() + 1;
this.onColumnsChanged_();
} else if (key === 'down') {
evt.preventDefault();
this.columnsInput.value = this.getColumns_() - 1;
this.onColumnsChanged_();
}
}
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();
};
})();

View File

@ -13,11 +13,17 @@
}
};
ns.FramesheetRenderer.prototype.renderAsCanvas = function () {
var canvas = this.createCanvas_();
ns.FramesheetRenderer.prototype.renderAsCanvas = function (columns) {
columns = columns || 1;
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);
};
})();

View File

@ -11,11 +11,17 @@
this.frames = frames;
};
ns.PiskelRenderer.prototype.renderAsCanvas = function () {
var canvas = this.createCanvas_();
ns.PiskelRenderer.prototype.renderAsCanvas = function (columns) {
columns = columns || 1;
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);
};
})();

View File

@ -12,7 +12,9 @@
ONION_SKIN : 'ONION_SKIN',
LAYER_PREVIEW : 'LAYER_PREVIEW',
LAYER_OPACITY : 'LAYER_OPACITY',
EXPORT_SCALING: 'EXPORT_SCALING',
EXPORT_PNG_BEST_FIT: 'EXPORT_PNG_BEST_FIT',
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 +32,10 @@
'ONION_SKIN' : false,
'LAYER_OPACITY' : 0.2,
'LAYER_PREVIEW' : true,
'EXPORT_SCALING' : 1,
'EXPORT_TAB' : 'png',
'EXPORT_PNG_BEST_FIT' : false,
'EXPORT_PNG_POWER_TWO' : false,
'EXPORT_SCALE' : 1,
'EXPORT_TAB' : 'gif',
'PEN_SIZE' : 1,
'RESIZE_SETTINGS': {
maintainRatio : true,

View File

@ -15,8 +15,8 @@
<input type="text" class="textfield resize-field resize-height" autocomplete="off" name="resize-height"/>
</div>
<div class="export-tabs">
<div class="export-tab" data-tab-id="png">PNG</div>
<div class="export-tab" data-tab-id="gif">GIF</div>
<div class="export-tab" data-tab-id="png">PNG</div>
<div class="export-tab" data-tab-id="zip">Zip</div>
<div class="export-tab" data-tab-id="misc">Others</div>
</div>

View File

@ -3,11 +3,11 @@
<div class="export-info">
Convert your sprite to an animated GIF. Opacity will not be preserved. Colors might be resampled.
</div>
<div class="export-panel-section">
<div class="export-panel-section export-panel-row">
<button type="button" class="button button-primary gif-download-button">Download</button>
<div>Download as an animated GIF.</div>
</div>
<div class="export-panel-section">
<div class="export-panel-section export-panel-row">
<button type="button" class="button button-primary gif-upload-button">Upload</button>
<div>Upload as an animated GIF to a public URL.</div>
</div>

View File

@ -1,8 +1,22 @@
<script type="text/html" id="templates/settings/export/png.html">
<div class="export-panel-png">
<div class="export-info">Export your animation as a PNG spritesheet containing all frames.</div>
<div class="export-panel-section">
<button type="button" class="button button-primary png-download-button">Download PNG</button>
<div class="export-panel-section png-export-layout-section">
<div style="color: gold; padding-bottom: 5px;">Spritesheet layout options:</div>
<div style="display: flex;">
<div style="flex: 1; line-height: 20px;">
<span>Columns</span>
<input type="text" class="textfield" id="png-export-columns" name="png-export-columns">
</div>
<div style="flex: 1; line-height: 20px;">
<label for="png-export-power-two">Power of 2</label>
<input type="checkbox" id="png-export-power-two" name="png-export-power-two">
</div>
</div>
</div>
<div class="export-panel-section export-panel-row">
<button type="button" class="button button-primary png-download-button">Download</button>
<span class="png-export-dimension-info"></span>
</div>
</div>
</script>