2021-07-22 20:26:40 +03:00
|
|
|
// REFACTOR: method of File class probably
|
|
|
|
|
2020-12-31 18:47:56 +03:00
|
|
|
/* This scripts contains all the code used to handle the sprite scaling */
|
|
|
|
// Should I keep the sprite ratio?
|
2020-09-19 18:04:46 +03:00
|
|
|
let keepRatio = true;
|
2020-12-31 18:47:56 +03:00
|
|
|
// Used to store the current ratio
|
2020-09-19 18:04:46 +03:00
|
|
|
let currentRatio;
|
2020-12-31 18:47:56 +03:00
|
|
|
// The currenty selected resizing algorithm (nearest-neighbor or bilinear-interpolation)
|
2020-09-22 13:29:58 +03:00
|
|
|
let currentAlgo = 'nearest-neighbor';
|
2020-12-31 18:47:56 +03:00
|
|
|
// Current resize data
|
2020-09-19 18:04:46 +03:00
|
|
|
let data = {width: 0, height: 0, widthPercentage: 100, heightPercentage: 100};
|
2020-12-31 18:47:56 +03:00
|
|
|
// Start resize data
|
2020-09-19 18:04:46 +03:00
|
|
|
let startData = {width: 0, height:0, widthPercentage: 100, heightPercentage: 100};
|
2020-09-18 16:22:10 +03:00
|
|
|
|
2020-12-31 18:47:56 +03:00
|
|
|
/** Opens the sprite resizing window
|
|
|
|
*
|
|
|
|
*/
|
2020-09-19 18:04:46 +03:00
|
|
|
function openResizeSpriteWindow() {
|
2020-12-31 18:47:56 +03:00
|
|
|
// Inits the sprie resize inputs
|
2020-09-22 13:29:58 +03:00
|
|
|
initResizeSpriteInputs();
|
2020-09-19 18:04:46 +03:00
|
|
|
|
2020-12-31 18:47:56 +03:00
|
|
|
// Computing the current ratio
|
2020-09-19 18:04:46 +03:00
|
|
|
currentRatio = layers[0].canvasSize[0] / layers[0].canvasSize[1];
|
|
|
|
|
2021-04-29 00:39:14 +03:00
|
|
|
console.log("Current ratio: " + currentRatio);
|
|
|
|
|
2020-12-31 18:47:56 +03:00
|
|
|
// Initializing the input fields
|
2020-09-19 18:04:46 +03:00
|
|
|
data.width = layers[0].canvasSize[0];
|
|
|
|
data.height = layers[1].canvasSize[1];
|
|
|
|
|
2020-09-22 13:29:58 +03:00
|
|
|
startData.width = parseInt(data.width);
|
|
|
|
startData.height = parseInt(data.height);
|
|
|
|
startData.heightPercentage = 100;
|
|
|
|
startData.widthPercentage = 100;
|
2020-09-19 18:04:46 +03:00
|
|
|
|
2020-12-31 18:47:56 +03:00
|
|
|
// Opening the pop up now that it's ready
|
2021-07-14 00:40:23 +03:00
|
|
|
Dialogue.showDialogue('resize-sprite');
|
2020-09-19 18:04:46 +03:00
|
|
|
}
|
|
|
|
|
2020-12-31 18:47:56 +03:00
|
|
|
/** Initalizes the input values and binds the elements to their events
|
|
|
|
*
|
|
|
|
*/
|
2020-09-19 18:04:46 +03:00
|
|
|
function initResizeSpriteInputs() {
|
|
|
|
document.getElementById("rs-width").value = layers[0].canvasSize[0];
|
|
|
|
document.getElementById("rs-height").value = layers[0].canvasSize[1];
|
|
|
|
|
|
|
|
document.getElementById("rs-width-percentage").value = 100;
|
|
|
|
document.getElementById("rs-height-percentage").value = 100;
|
|
|
|
|
|
|
|
document.getElementById("rs-keep-ratio").checked = true;
|
|
|
|
|
|
|
|
document.getElementById("rs-width").addEventListener("change", changedWidth);
|
|
|
|
document.getElementById("rs-height").addEventListener("change", changedHeight);
|
|
|
|
document.getElementById("rs-width-percentage").addEventListener("change", changedWidthPercentage);
|
|
|
|
document.getElementById("rs-height-percentage").addEventListener("change", changedHeightPercentage);
|
|
|
|
|
|
|
|
document.getElementById("resize-sprite-confirm").addEventListener("click", resizeSprite);
|
|
|
|
document.getElementById("rs-keep-ratio").addEventListener("click", toggleRatio);
|
2020-09-22 13:29:58 +03:00
|
|
|
document.getElementById("resize-algorithm-combobox").addEventListener("change", changedAlgorithm);
|
2020-09-19 18:04:46 +03:00
|
|
|
}
|
|
|
|
|
2020-12-31 18:47:56 +03:00
|
|
|
/** Resizes (scales) the sprite
|
|
|
|
*
|
|
|
|
* @param {*} event
|
|
|
|
* @param {*} ratio Keeps infos about the x ratio and y ratio
|
|
|
|
*/
|
2020-09-22 15:17:31 +03:00
|
|
|
function resizeSprite(event, ratio) {
|
2020-12-31 18:47:56 +03:00
|
|
|
// Old data
|
2020-09-22 15:17:31 +03:00
|
|
|
let oldWidth, oldHeight;
|
2020-12-31 18:47:56 +03:00
|
|
|
// New data
|
2020-09-21 11:39:37 +03:00
|
|
|
let newWidth, newHeight;
|
2020-12-31 18:47:56 +03:00
|
|
|
// Current imageDatas
|
2020-09-22 15:17:31 +03:00
|
|
|
let rsImageDatas = [];
|
2020-12-31 18:47:56 +03:00
|
|
|
// Index that will be used a few lines below
|
2020-09-21 13:33:45 +03:00
|
|
|
let layerIndex = 0;
|
2020-12-31 18:47:56 +03:00
|
|
|
// Copy of the imageDatas that will be stored in the history
|
2020-09-22 15:17:31 +03:00
|
|
|
let imageDatasCopy = [];
|
2020-09-21 11:39:37 +03:00
|
|
|
|
2020-09-22 15:17:31 +03:00
|
|
|
oldWidth = layers[0].canvasSize[0];
|
|
|
|
oldHeight = layers[1].canvasSize[1];
|
2020-09-21 11:39:37 +03:00
|
|
|
rcPivot = "middle";
|
2020-09-22 15:17:31 +03:00
|
|
|
|
2020-09-21 11:39:37 +03:00
|
|
|
// Updating values if the user didn't press enter
|
|
|
|
switch (document.activeElement.id) {
|
|
|
|
case "rs-width-percentage":
|
|
|
|
changedWidthPercentage();
|
|
|
|
break;
|
|
|
|
case "rs-width":
|
|
|
|
changedWidth();
|
|
|
|
break;
|
|
|
|
case "rs-height-percentage":
|
|
|
|
changedHeightPercentage();
|
|
|
|
break;
|
|
|
|
case "rs-height":
|
|
|
|
changedHeight();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// In this case everything has been updated correctly
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-12-31 18:47:56 +03:00
|
|
|
// Computing newWidth and newHeight
|
2020-09-22 15:17:31 +03:00
|
|
|
if (ratio == null) {
|
|
|
|
newWidth = data.width;
|
|
|
|
newHeight = data.height;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
newWidth = layers[0].canvasSize[0] * ratio[0];
|
|
|
|
newHeight = layers[1].canvasSize[1] * ratio[1];
|
|
|
|
}
|
|
|
|
|
2020-09-21 13:33:45 +03:00
|
|
|
// Get all the image datas
|
|
|
|
for (let i=0; i<layers.length; i++) {
|
|
|
|
if (layers[i].menuEntry != null) {
|
2020-09-22 15:17:31 +03:00
|
|
|
rsImageDatas.push(layers[i].context.getImageData(
|
2020-09-21 13:33:45 +03:00
|
|
|
0, 0, layers[0].canvasSize[0], layers[0].canvasSize[1])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-29 13:50:59 +03:00
|
|
|
// event is null when the user is undoing
|
|
|
|
if (event != null) {
|
2020-12-31 18:47:56 +03:00
|
|
|
// Copying the image data
|
2020-09-22 15:17:31 +03:00
|
|
|
imageDatasCopy = rsImageDatas.slice();
|
2020-12-31 18:47:56 +03:00
|
|
|
// Saving the history
|
2021-07-15 18:10:07 +03:00
|
|
|
new HistoryState().ResizeSprite(newWidth / oldWidth, newHeight / oldHeight, currentAlgo, imageDatasCopy);
|
2020-09-22 15:17:31 +03:00
|
|
|
}
|
|
|
|
|
2020-09-21 11:39:37 +03:00
|
|
|
// Resizing the canvas
|
|
|
|
resizeCanvas(null, {x: newWidth, y: newHeight});
|
2020-09-21 13:33:45 +03:00
|
|
|
|
2020-09-21 11:39:37 +03:00
|
|
|
// Put the image datas on the new canvases
|
2020-09-21 13:33:45 +03:00
|
|
|
for (let i=0; i<layers.length; i++) {
|
|
|
|
if (layers[i].menuEntry != null) {
|
|
|
|
layers[i].context.putImageData(
|
2020-09-22 15:17:31 +03:00
|
|
|
resizeImageData(rsImageDatas[layerIndex], newWidth, newHeight, currentAlgo), 0, 0
|
2020-09-21 13:33:45 +03:00
|
|
|
);
|
2020-09-22 15:17:31 +03:00
|
|
|
layers[i].updateLayerPreview();
|
2020-09-21 13:33:45 +03:00
|
|
|
layerIndex++;
|
|
|
|
}
|
|
|
|
}
|
2020-09-22 13:29:58 +03:00
|
|
|
|
|
|
|
// Updating start values when I finish scaling the sprite
|
|
|
|
// OPTIMIZABLE? Can't I just assign data to startData? Is js smart enough to understand?
|
2020-09-22 15:17:31 +03:00
|
|
|
if (ratio == null) {
|
|
|
|
startData.width = data.width;
|
|
|
|
startData.height = data.height;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
startData.width = layers[0].canvasSize[0];
|
|
|
|
startData.height = layers[0].canvasSize[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
startData.widthPercentage = 100;
|
|
|
|
startData.heightPercentage = 100;
|
2020-09-22 13:29:58 +03:00
|
|
|
|
2021-07-14 00:40:23 +03:00
|
|
|
Dialogue.closeDialogue();
|
2020-09-19 18:04:46 +03:00
|
|
|
}
|
|
|
|
|
2020-12-31 18:47:56 +03:00
|
|
|
/* Trust me, the math for the functions below works. If you want to optimize them feel free to have a look, though */
|
|
|
|
|
|
|
|
/** Fired when the input field for width is changed. Updates th othe input fields consequently
|
|
|
|
*
|
|
|
|
* @param {*} event
|
|
|
|
*/
|
2020-09-19 18:04:46 +03:00
|
|
|
function changedWidth(event) {
|
|
|
|
let oldValue = data.width;
|
|
|
|
let ratio;
|
|
|
|
let percentageRatio;
|
2020-09-21 11:39:37 +03:00
|
|
|
let newHeight, newHeightPerc, newWidthPerc;
|
|
|
|
|
2020-09-19 18:04:46 +03:00
|
|
|
data.width = event.target.value;
|
|
|
|
delta = data.width - oldValue;
|
|
|
|
|
|
|
|
ratio = data.width / oldValue;
|
|
|
|
|
2020-09-21 11:39:37 +03:00
|
|
|
newHeight = data.width / currentRatio;
|
|
|
|
newHeightPerc = (newHeight * 100) / startData.height;
|
|
|
|
newWidthPerc = (data.width * 100) / startData.width;
|
|
|
|
|
2020-09-19 18:04:46 +03:00
|
|
|
if (keepRatio) {
|
2020-09-21 11:39:37 +03:00
|
|
|
document.getElementById("rs-height").value = newHeight;
|
|
|
|
data.height = newHeight;
|
|
|
|
|
|
|
|
document.getElementById("rs-height-percentage").value = newHeightPerc;
|
|
|
|
data.heightPercentage = newHeightPerc;
|
2020-09-19 18:04:46 +03:00
|
|
|
}
|
|
|
|
|
2020-09-21 11:39:37 +03:00
|
|
|
document.getElementById("rs-width-percentage").value = newWidthPerc;
|
2020-09-19 18:04:46 +03:00
|
|
|
}
|
|
|
|
|
2020-12-31 18:47:56 +03:00
|
|
|
/**Fired when the input field for width is changed. Updates the other input fields consequently
|
|
|
|
*
|
|
|
|
* @param {*} event
|
|
|
|
*/
|
2020-09-19 18:04:46 +03:00
|
|
|
function changedHeight(event) {
|
2020-09-22 13:29:58 +03:00
|
|
|
let oldValue = 100;
|
2020-09-19 18:04:46 +03:00
|
|
|
let ratio;
|
2020-09-21 11:39:37 +03:00
|
|
|
let newWidth, newWidthPerc, newHeightPerc;
|
2020-09-19 18:04:46 +03:00
|
|
|
|
|
|
|
data.height = event.target.value;
|
|
|
|
delta = data.height - oldValue;
|
|
|
|
|
|
|
|
ratio = data.height / oldValue;
|
|
|
|
|
2020-09-21 11:39:37 +03:00
|
|
|
newWidth = data.height * currentRatio;
|
|
|
|
newWidthPerc = (newWidth * 100) / startData.width;
|
|
|
|
newHeightPerc = (data.height * 100) / startData.height;
|
|
|
|
|
2020-09-19 18:04:46 +03:00
|
|
|
if (keepRatio) {
|
2020-09-21 11:39:37 +03:00
|
|
|
document.getElementById("rs-width").value = newWidth;
|
|
|
|
data.width = newWidth;
|
|
|
|
|
|
|
|
document.getElementById("rs-width-percentage").value = newWidthPerc;
|
|
|
|
data.widthPercentage = newWidthPerc;
|
2020-09-19 18:04:46 +03:00
|
|
|
}
|
|
|
|
|
2020-09-21 11:39:37 +03:00
|
|
|
document.getElementById("rs-height-percentage").value = newHeightPerc;
|
|
|
|
data.heightPercentage = newHeightPerc;
|
2020-09-19 18:04:46 +03:00
|
|
|
}
|
|
|
|
|
2020-12-31 18:47:56 +03:00
|
|
|
/**Fired when the input field for width percentage is changed. Updates the other input fields consequently
|
|
|
|
*
|
|
|
|
* @param {*} event
|
|
|
|
*/
|
2020-09-19 18:04:46 +03:00
|
|
|
function changedWidthPercentage(event) {
|
2020-09-22 13:29:58 +03:00
|
|
|
let oldValue = 100;
|
2020-09-19 18:04:46 +03:00
|
|
|
let ratio;
|
2020-09-21 11:39:37 +03:00
|
|
|
let newWidth, newHeight, newHeightPerc;
|
2020-09-19 18:04:46 +03:00
|
|
|
|
|
|
|
data.widthPercentage = event.target.value;
|
|
|
|
delta = data.widthPercentage - oldValue;
|
|
|
|
|
|
|
|
ratio = data.widthPercentage / oldValue;
|
|
|
|
|
2020-09-22 13:29:58 +03:00
|
|
|
console.log("old value: " + oldValue + ", ratio: " + ratio);
|
|
|
|
|
|
|
|
newHeight = startData.height * ratio;
|
2021-04-29 00:39:14 +03:00
|
|
|
newHeightPerc = data.widthPercentage;
|
2020-09-22 13:29:58 +03:00
|
|
|
newWidth = startData.width * ratio;
|
2020-09-21 11:39:37 +03:00
|
|
|
|
2020-09-19 18:04:46 +03:00
|
|
|
if (keepRatio) {
|
2020-09-21 11:39:37 +03:00
|
|
|
document.getElementById("rs-height-percentage").value = newHeightPerc;
|
|
|
|
data.heightPercentage = newHeightPerc;
|
|
|
|
|
|
|
|
document.getElementById("rs-height").value = newHeight
|
|
|
|
data.height = newHeight;
|
2020-09-19 18:04:46 +03:00
|
|
|
}
|
|
|
|
|
2020-09-21 11:39:37 +03:00
|
|
|
document.getElementById("rs-width").value = newWidth;
|
|
|
|
data.width = newWidth;
|
2020-09-19 18:04:46 +03:00
|
|
|
}
|
|
|
|
|
2020-12-31 18:47:56 +03:00
|
|
|
/**Fired when the input field for height percentage is changed. Updates the other input fields consequently
|
|
|
|
*
|
|
|
|
* @param {*} event
|
|
|
|
*/
|
2020-09-19 18:04:46 +03:00
|
|
|
function changedHeightPercentage(event) {
|
|
|
|
let oldValue = data.heightPercentage;
|
|
|
|
let ratio;
|
2020-09-21 11:39:37 +03:00
|
|
|
let newHeight, newWidth, newWidthPerc;
|
2020-09-19 18:04:46 +03:00
|
|
|
|
|
|
|
data.heightPercentage = event.target.value;
|
|
|
|
delta = data.heightPercentage - oldValue;
|
|
|
|
|
|
|
|
ratio = data.heightPercentage / oldValue;
|
|
|
|
|
2020-09-22 13:29:58 +03:00
|
|
|
newWidth = startData.width * ratio;
|
2021-04-29 00:39:14 +03:00
|
|
|
newWidthPerc = data.heightPercentage;
|
2020-09-22 13:29:58 +03:00
|
|
|
newHeight = startData.height * ratio;
|
2020-09-21 11:39:37 +03:00
|
|
|
|
2020-09-19 18:04:46 +03:00
|
|
|
if (keepRatio) {
|
|
|
|
document.getElementById("rs-width-percentage").value = data.heightPercentage * currentRatio;
|
2020-09-21 11:39:37 +03:00
|
|
|
data.widthPercentage = newWidthPerc;
|
|
|
|
|
|
|
|
document.getElementById("rs-width").value = newWidth;
|
|
|
|
data.width = newWidth;
|
2020-09-19 18:04:46 +03:00
|
|
|
}
|
|
|
|
|
2020-09-21 11:39:37 +03:00
|
|
|
document.getElementById("rs-height").value = newHeight;
|
|
|
|
data.height = newHeight;
|
2020-09-19 18:04:46 +03:00
|
|
|
}
|
|
|
|
|
2020-12-31 18:47:56 +03:00
|
|
|
/** Toggles the keepRatio value (fired by the checkbox in the pop up window)
|
|
|
|
*
|
|
|
|
* @param {*} event
|
|
|
|
*/
|
2020-09-19 18:04:46 +03:00
|
|
|
function toggleRatio(event) {
|
|
|
|
keepRatio = !keepRatio;
|
2020-09-22 13:29:58 +03:00
|
|
|
}
|
|
|
|
|
2020-12-31 18:47:56 +03:00
|
|
|
/** Changes the scaling algorithm (fired by the combobox in the pop up window)
|
|
|
|
*
|
|
|
|
* @param {*} event
|
|
|
|
*/
|
2020-09-22 13:29:58 +03:00
|
|
|
function changedAlgorithm(event) {
|
|
|
|
currentAlgo = event.target.value;
|
2021-11-01 14:01:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Resizes an imageData depending on the algorithm and on the new width and height
|
|
|
|
*
|
|
|
|
* @param {*} image The imageData to scale
|
|
|
|
* @param {*} width The new width of the imageData
|
|
|
|
* @param {*} height The new height of the imageData
|
|
|
|
* @param {*} algorithm Scaling algorithm chosen by the user in the dialogue
|
|
|
|
*/
|
|
|
|
function resizeImageData (image, width, height, algorithm) {
|
|
|
|
algorithm = algorithm || 'bilinear-interpolation'
|
|
|
|
|
|
|
|
let resize;
|
|
|
|
switch (algorithm) {
|
|
|
|
case 'nearest-neighbor': resize = nearestNeighbor; break
|
|
|
|
case 'bilinear-interpolation': resize = bilinearInterpolation; break
|
|
|
|
default: return image;
|
|
|
|
}
|
|
|
|
|
|
|
|
const result = new ImageData(width, height)
|
|
|
|
|
|
|
|
resize(image, result)
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Nearest neighbor algorithm to scale a sprite
|
|
|
|
*
|
|
|
|
* @param {*} src The source imageData
|
|
|
|
* @param {*} dst The destination imageData
|
|
|
|
*/
|
|
|
|
function nearestNeighbor (src, dst) {
|
|
|
|
let pos = 0
|
|
|
|
|
|
|
|
// Just applying the nearest neighbor algorithm
|
|
|
|
for (let y = 0; y < dst.height; y++) {
|
|
|
|
for (let x = 0; x < dst.width; x++) {
|
|
|
|
const srcX = Math.floor(x * src.width / dst.width)
|
|
|
|
const srcY = Math.floor(y * src.height / dst.height)
|
|
|
|
|
|
|
|
let srcPos = ((srcY * src.width) + srcX) * 4
|
|
|
|
|
|
|
|
dst.data[pos++] = src.data[srcPos++] // R
|
|
|
|
dst.data[pos++] = src.data[srcPos++] // G
|
|
|
|
dst.data[pos++] = src.data[srcPos++] // B
|
|
|
|
dst.data[pos++] = src.data[srcPos++] // A
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Bilinear interpolation used to scale a sprite
|
|
|
|
*
|
|
|
|
* @param {*} src The source imageData
|
|
|
|
* @param {*} dst The destination imageData
|
|
|
|
*/
|
|
|
|
function bilinearInterpolation (src, dst) {
|
|
|
|
// Applying the bilinear interpolation algorithm
|
|
|
|
|
|
|
|
function interpolate (k, kMin, kMax, vMin, vMax) {
|
|
|
|
return Math.round((k - kMin) * vMax + (kMax - k) * vMin)
|
|
|
|
}
|
|
|
|
|
|
|
|
function interpolateHorizontal (offset, x, y, xMin, xMax) {
|
|
|
|
const vMin = src.data[((y * src.width + xMin) * 4) + offset]
|
|
|
|
if (xMin === xMax) return vMin
|
|
|
|
|
|
|
|
const vMax = src.data[((y * src.width + xMax) * 4) + offset]
|
|
|
|
return interpolate(x, xMin, xMax, vMin, vMax)
|
|
|
|
}
|
|
|
|
|
|
|
|
function interpolateVertical (offset, x, xMin, xMax, y, yMin, yMax) {
|
|
|
|
const vMin = interpolateHorizontal(offset, x, yMin, xMin, xMax)
|
|
|
|
if (yMin === yMax) return vMin
|
|
|
|
|
|
|
|
const vMax = interpolateHorizontal(offset, x, yMax, xMin, xMax)
|
|
|
|
return interpolate(y, yMin, yMax, vMin, vMax)
|
|
|
|
}
|
|
|
|
|
|
|
|
let pos = 0
|
|
|
|
|
|
|
|
for (let y = 0; y < dst.height; y++) {
|
|
|
|
for (let x = 0; x < dst.width; x++) {
|
|
|
|
const srcX = x * src.width / dst.width
|
|
|
|
const srcY = y * src.height / dst.height
|
|
|
|
|
|
|
|
const xMin = Math.floor(srcX)
|
|
|
|
const yMin = Math.floor(srcY)
|
|
|
|
|
|
|
|
const xMax = Math.min(Math.ceil(srcX), src.width - 1)
|
|
|
|
const yMax = Math.min(Math.ceil(srcY), src.height - 1)
|
|
|
|
|
|
|
|
dst.data[pos++] = interpolateVertical(0, srcX, xMin, xMax, srcY, yMin, yMax) // R
|
|
|
|
dst.data[pos++] = interpolateVertical(1, srcX, xMin, xMax, srcY, yMin, yMax) // G
|
|
|
|
dst.data[pos++] = interpolateVertical(2, srcX, xMin, xMax, srcY, yMin, yMax) // B
|
|
|
|
dst.data[pos++] = interpolateVertical(3, srcX, xMin, xMax, srcY, yMin, yMax) // A
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|