mirror of
https://github.com/piskelapp/piskel.git
synced 2023-08-10 21:12:52 +03:00
export controller: add synchronized rows input, remove poweroftwo checkbox
This commit is contained in:
parent
b39b3befa1
commit
cd36a57a1a
@ -14,6 +14,10 @@
|
||||
height: 23px;
|
||||
}
|
||||
|
||||
.textfield[readonly="true"] {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.textfield[disabled=disabled] {
|
||||
background : #3a3a3a;
|
||||
}
|
||||
|
@ -137,11 +137,14 @@
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.export-info {
|
||||
.export-panel-header {
|
||||
padding: 10px 5px 0px;
|
||||
font-weight: normal;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.export-info {
|
||||
font-style: italic;
|
||||
text-shadow: none;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.export-panel-section {
|
||||
@ -167,13 +170,13 @@
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.png-export-dimension-info {
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
.png-export-dimension-info,
|
||||
.png-export-datauri-info {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#png-export-columns {
|
||||
#png-export-columns,
|
||||
#png-export-rows {
|
||||
/* Override default textfield padding-right to keep the number spinners
|
||||
aligned to the right. */
|
||||
padding-right: 0;
|
||||
|
@ -1,12 +1,13 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.controller.settings.exportimage');
|
||||
|
||||
var dimensionInfoPattern = '{{width}} x {{height}} px, {{frames}}<br/>{{rows}}, {{columns}}.';
|
||||
var dimensionInfoPattern = '{{width}} x {{height}} px, {{frames}}<br/>{{columns}}, {{rows}}.';
|
||||
|
||||
// Shortcut to pskl.utils.Template.replace
|
||||
var replace = pskl.utils.Template.replace;
|
||||
|
||||
// Helper to return "X items" or "1 item" if X is 1.
|
||||
// Helper to return "X items" or "1 item" if X is 1. Can be cnsidered as an overkill,
|
||||
// but the one-liner equivalent is hard to read.
|
||||
var pluralize = function (word, count) {
|
||||
if (count === 1) {
|
||||
return '1 ' + word;
|
||||
@ -14,11 +15,6 @@
|
||||
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;
|
||||
@ -30,16 +26,19 @@
|
||||
ns.PngExportController.prototype.init = function () {
|
||||
this.layoutContainer = document.querySelector('.png-export-layout-section');
|
||||
this.dimensionInfo = document.querySelector('.png-export-dimension-info');
|
||||
|
||||
this.rowsInput = document.querySelector('#png-export-rows');
|
||||
this.columnsInput = document.querySelector('#png-export-columns');
|
||||
this.powerTwo = document.querySelector('#png-export-power-two');
|
||||
|
||||
var downloadButton = document.querySelector('.png-download-button');
|
||||
var dataUriButton = document.querySelector('.datauri-open-button');
|
||||
|
||||
this.initLayoutSection_();
|
||||
this.updateDimensionLabel_();
|
||||
|
||||
this.addEventListener(this.columnsInput, 'input', this.onColumnsInput_);
|
||||
this.addEventListener(downloadButton, 'click', this.onDownloadClick_);
|
||||
this.addEventListener(this.columnsInput, 'input', this.onColumnsChanged_);
|
||||
this.addEventListener(this.powerTwo, 'change', this.onPowerTwoChanged_);
|
||||
this.addEventListener(dataUriButton, 'click', this.onDataUriClick_);
|
||||
$.subscribe(Events.EXPORT_SCALE_CHANGED, this.onScaleChanged_);
|
||||
};
|
||||
|
||||
@ -57,8 +56,9 @@
|
||||
// Hide the layout section if only one frame is defined.
|
||||
this.layoutContainer.style.display = 'none';
|
||||
} else {
|
||||
this.columnsInput.setAttribute('max', frames);
|
||||
this.columnsInput.value = this.getBestFit_();
|
||||
this.powerTwo.checked = pskl.UserSettings.get('EXPORT_PNG_POWER_TWO');
|
||||
this.onColumnsInput_();
|
||||
}
|
||||
};
|
||||
|
||||
@ -69,15 +69,10 @@
|
||||
var height = this.piskelController.getHeight() * zoom;
|
||||
|
||||
var columns = this.getColumns_();
|
||||
var rows = Math.ceil(frames / columns);
|
||||
var rows = this.getRows_();
|
||||
width = columns * width;
|
||||
height = rows * height;
|
||||
|
||||
if (this.isPowerTwoEnabled_()) {
|
||||
width = getNearestPowerOfTwo(width);
|
||||
height = getNearestPowerOfTwo(height);
|
||||
}
|
||||
|
||||
this.dimensionInfo.innerHTML = replace(dimensionInfoPattern, {
|
||||
width: width,
|
||||
height: height,
|
||||
@ -91,42 +86,51 @@
|
||||
return parseInt(this.columnsInput.value || 1, 10);
|
||||
};
|
||||
|
||||
ns.PngExportController.prototype.getRows_ = function () {
|
||||
return parseInt(this.rowsInput.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');
|
||||
return pskl.utils.Math.minmax(bestFit, 1, frameCount);
|
||||
};
|
||||
|
||||
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;
|
||||
/**
|
||||
* Synchronise column and row inputs, called everytime a user input updates one of the
|
||||
* two inputs by the SynchronizedInputs widget.
|
||||
*/
|
||||
ns.PngExportController.prototype.onColumnsInput_ = function () {
|
||||
var value = this.columnsInput.value;
|
||||
if (value === '') {
|
||||
// Skip the synchronization if the input is empty.
|
||||
return;
|
||||
}
|
||||
|
||||
value = parseInt(value, 10);
|
||||
if (isNaN(value)) {
|
||||
value = 1;
|
||||
}
|
||||
|
||||
// Force the value to be in bounds, in the user tried to update it by directly typing
|
||||
// a value.
|
||||
value = pskl.utils.Math.minmax(value, 1, this.piskelController.getFrameCount());
|
||||
this.columnsInput.value = value;
|
||||
|
||||
// Update readonly rowsInput
|
||||
this.rowsInput.value = Math.ceil(this.piskelController.getFrameCount() / value);
|
||||
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';
|
||||
|
||||
ns.PngExportController.prototype.createPngSpritesheet_ = function () {
|
||||
var renderer = new pskl.rendering.PiskelRenderer(this.piskelController);
|
||||
var outputCanvas = renderer.renderAsCanvas(this.getColumns_());
|
||||
var outputCanvas = renderer.renderAsCanvas(this.getColumns_(), this.getRows_());
|
||||
var width = outputCanvas.width;
|
||||
var height = outputCanvas.height;
|
||||
|
||||
@ -135,15 +139,24 @@
|
||||
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;
|
||||
}
|
||||
return outputCanvas;
|
||||
};
|
||||
|
||||
pskl.utils.BlobUtils.canvasToBlob(outputCanvas, function(blob) {
|
||||
ns.PngExportController.prototype.onDownloadClick_ = function (evt) {
|
||||
// Create PNG export.
|
||||
var canvas = this.createPngSpritesheet_();
|
||||
|
||||
// Generate file name
|
||||
var name = this.piskelController.getPiskel().getDescriptor().name;
|
||||
var fileName = name + '.png';
|
||||
|
||||
// Transform to blob and start download.
|
||||
pskl.utils.BlobUtils.canvasToBlob(canvas, function(blob) {
|
||||
pskl.utils.FileUtils.downloadAsFile(blob, fileName);
|
||||
});
|
||||
};
|
||||
|
||||
ns.PngExportController.prototype.onDataUriClick_ = function (evt) {
|
||||
window.open(this.createPngSpritesheet_().toDataURL('image/png'));
|
||||
};
|
||||
})();
|
||||
|
@ -12,7 +12,6 @@
|
||||
ONION_SKIN : 'ONION_SKIN',
|
||||
LAYER_PREVIEW : 'LAYER_PREVIEW',
|
||||
LAYER_OPACITY : 'LAYER_OPACITY',
|
||||
EXPORT_PNG_POWER_TWO: 'EXPORT_PNG_POWER_TWO',
|
||||
EXPORT_SCALE: 'EXPORT_SCALE',
|
||||
EXPORT_TAB: 'EXPORT_TAB',
|
||||
PEN_SIZE : 'PEN_SIZE',
|
||||
@ -31,7 +30,6 @@
|
||||
'ONION_SKIN' : false,
|
||||
'LAYER_OPACITY' : 0.2,
|
||||
'LAYER_PREVIEW' : true,
|
||||
'EXPORT_PNG_POWER_TWO' : false,
|
||||
'EXPORT_SCALE' : 1,
|
||||
'EXPORT_TAB' : 'gif',
|
||||
'PEN_SIZE' : 1,
|
||||
|
@ -19,31 +19,24 @@
|
||||
this.initHeight = options.initHeight;
|
||||
this.onChange = options.onChange;
|
||||
|
||||
this.syncEnabled = true;
|
||||
this.lastInput = this.widthInput;
|
||||
this.synchronizedInputs = new ns.SynchronizedInputs({
|
||||
leftInput: this.widthInput,
|
||||
rightInput: this.heightInput,
|
||||
synchronize: this.synchronize_.bind(this)
|
||||
});
|
||||
|
||||
this.disableSync = this.synchronizedInputs.disableSync.bind(this.synchronizedInputs);
|
||||
this.enableSync = this.synchronizedInputs.enableSync.bind(this.synchronizedInputs);
|
||||
|
||||
this.widthInput.value = this.initWidth;
|
||||
this.heightInput.value = this.initHeight;
|
||||
|
||||
pskl.utils.Event.addEventListener(this.widthInput, 'keyup', this.onSizeInputKeyUp_, this);
|
||||
pskl.utils.Event.addEventListener(this.heightInput, 'keyup', this.onSizeInputKeyUp_, this);
|
||||
};
|
||||
|
||||
ns.SizeInput.prototype.destroy = function () {
|
||||
pskl.utils.Event.removeAllEventListeners(this);
|
||||
this.synchronizedInputs.destroy();
|
||||
|
||||
this.widthInput = null;
|
||||
this.heightInput = null;
|
||||
this.lastInput = null;
|
||||
};
|
||||
|
||||
ns.SizeInput.prototype.enableSync = function () {
|
||||
this.syncEnabled = true;
|
||||
this.synchronize_(this.lastInput);
|
||||
};
|
||||
|
||||
ns.SizeInput.prototype.disableSync = function () {
|
||||
this.syncEnabled = false;
|
||||
};
|
||||
|
||||
ns.SizeInput.prototype.setWidth = function (width) {
|
||||
@ -56,14 +49,6 @@
|
||||
this.synchronize_(this.heightInput);
|
||||
};
|
||||
|
||||
ns.SizeInput.prototype.onSizeInputKeyUp_ = function (evt) {
|
||||
var target = evt.target;
|
||||
if (this.syncEnabled) {
|
||||
this.synchronize_(target);
|
||||
}
|
||||
this.lastInput = target;
|
||||
};
|
||||
|
||||
/**
|
||||
* Based on the value of the provided sizeInput (considered as emitter)
|
||||
* update the value of the other sizeInput to match the current width/height ratio
|
||||
|
40
src/js/widgets/SynchronizedInputs.js
Normal file
40
src/js/widgets/SynchronizedInputs.js
Normal file
@ -0,0 +1,40 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.widgets');
|
||||
|
||||
ns.SynchronizedInputs = function (options) {
|
||||
this.leftInput = options.leftInput;
|
||||
this.rightInput = options.rightInput;
|
||||
this.synchronize = options.synchronize;
|
||||
|
||||
this.syncEnabled = true;
|
||||
this.lastInput = this.leftInput;
|
||||
|
||||
pskl.utils.Event.addEventListener(this.leftInput, 'input', this.onInput_, this);
|
||||
pskl.utils.Event.addEventListener(this.rightInput, 'input', this.onInput_, this);
|
||||
};
|
||||
|
||||
ns.SynchronizedInputs.prototype.destroy = function () {
|
||||
pskl.utils.Event.removeAllEventListeners(this);
|
||||
|
||||
this.leftInput = null;
|
||||
this.rightInput = null;
|
||||
this.lastInput = null;
|
||||
};
|
||||
|
||||
ns.SynchronizedInputs.prototype.enableSync = function () {
|
||||
this.syncEnabled = true;
|
||||
this.synchronize(this.lastInput);
|
||||
};
|
||||
|
||||
ns.SynchronizedInputs.prototype.disableSync = function () {
|
||||
this.syncEnabled = false;
|
||||
};
|
||||
|
||||
ns.SynchronizedInputs.prototype.onInput_ = function (evt) {
|
||||
var target = evt.target;
|
||||
if (this.syncEnabled) {
|
||||
this.synchronize(target);
|
||||
}
|
||||
this.lastInput = target;
|
||||
};
|
||||
})();
|
@ -142,6 +142,7 @@
|
||||
"js/widgets/ColorsList.js",
|
||||
"js/widgets/HslRgbColorPicker.js",
|
||||
"js/widgets/SizeInput.js",
|
||||
"js/widgets/SynchronizedInputs.js",
|
||||
|
||||
// Services
|
||||
"js/service/storage/StorageService.js",
|
||||
|
@ -1,15 +1,15 @@
|
||||
<script type="text/html" id="templates/settings/export/gif.html">
|
||||
<div class="export-panel-gif">
|
||||
<div class="export-info">
|
||||
<div class="export-panel-header export-info">
|
||||
Convert your sprite to an animated GIF. Opacity will not be preserved. Colors might be resampled.
|
||||
</div>
|
||||
<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 class="export-info">Download as an animated GIF.</div>
|
||||
</div>
|
||||
<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 class="export-info">Upload as an animated GIF to a public URL.</div>
|
||||
</div>
|
||||
<div class="gif-upload">
|
||||
<div class="gif-export-preview"></div>
|
||||
|
@ -1,12 +1,12 @@
|
||||
<script type="text/html" id="templates/settings/export/misc.html">
|
||||
<div class="export-panel-misc">
|
||||
<div class="export-info">
|
||||
<div class="export-panel-header export-info">
|
||||
Less usual export formats. Feedback and improvements welcome.
|
||||
</div>
|
||||
<div class="export-panel-section">
|
||||
<div style="padding-bottom: 5px">
|
||||
<span style="color: gold;">Export as C File: </span>
|
||||
<span style="font-weight: normal; text-shadow: none; font-style: italic">
|
||||
<span class="export-info">
|
||||
C file with frame rendered as array.
|
||||
</span>
|
||||
</div>
|
||||
|
@ -1,22 +1,26 @@
|
||||
<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-header export-info">Export your animation as a PNG spritesheet containing all frames.</div>
|
||||
<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;">
|
||||
<div style="display: flex; line-height: 20px;">
|
||||
<div style="flex: 1;">
|
||||
<span>Columns</span>
|
||||
<input type="number" class="textfield" id="png-export-columns" name="png-export-columns">
|
||||
<input type="number" min="1" 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 style="flex: 1;">
|
||||
<span>Rows</span>
|
||||
<input readonly="true" class="textfield" id="png-export-rows" name="png-export-rows">
|
||||
</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>
|
||||
<span class="png-export-dimension-info export-info"></span>
|
||||
</div>
|
||||
<div class="export-panel-section export-panel-row png-export-datauri-section">
|
||||
<button type="button" style="white-space: nowrap;" class="button button-primary datauri-open-button">To data-uri</button>
|
||||
<span class="png-export-datauri-info export-info">Open the PNG export in your browser as a data-uri</span>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
@ -1,6 +1,6 @@
|
||||
<script type="text/html" id="templates/settings/export/zip.html">
|
||||
<div class="export-panel-zip">
|
||||
<div class="export-info">ZIP archive containing one PNG for each frame. File names will start with the prefix below.</div>
|
||||
<div class="export-panel-header export-info">ZIP archive containing one PNG for each frame. File names will start with the prefix below.</div>
|
||||
<div class="export-panel-section">
|
||||
<div style="display: flex; line-height: 23px;">
|
||||
<label style="flex-shrink: 0; padding-right:5px;">Prefix</label>
|
||||
|
Loading…
Reference in New Issue
Block a user