mirror of
https://github.com/lospec/pixel-editor.git
synced 2023-08-10 21:12:51 +03:00
Finished refactoring, moved sprite scaling functions in File
This commit is contained in:
parent
b2f5521750
commit
21dd47c2b0
@ -52,7 +52,6 @@ const EditorState = (() => {
|
||||
}
|
||||
|
||||
function chooseMode() {
|
||||
console.log("Here");
|
||||
let prevMode = pixelEditorMode.toLowerCase();
|
||||
|
||||
if (pixelEditorMode === "Basic") {
|
||||
|
375
js/File.js
375
js/File.js
@ -22,6 +22,18 @@ class File {
|
||||
// Border offsets
|
||||
rcBorders = {left: 0, right: 0, top: 0, bottom: 0};
|
||||
|
||||
// Sprite scaling attributes
|
||||
// Should I keep the sprite ratio?
|
||||
keepRatio = true;
|
||||
// Used to store the current ratio
|
||||
currentRatio = undefined;
|
||||
// The currenty selected resizing algorithm (nearest-neighbor or bilinear-interpolation)
|
||||
currentAlgo = 'nearest-neighbor';
|
||||
// Current resize data
|
||||
data = {width: 0, height: 0, widthPercentage: 100, heightPercentage: 100};
|
||||
// Start resize data
|
||||
startData = {width: 0, height:0, widthPercentage: 100, heightPercentage: 100};
|
||||
|
||||
// Sprite scaling attributes
|
||||
|
||||
openResizeCanvasWindow() {
|
||||
@ -97,7 +109,6 @@ class File {
|
||||
* @param {*} saveHistory Should I save the history? You shouldn't if you're undoing
|
||||
*/
|
||||
resizeCanvas(event, size, customData, saveHistory = true) {
|
||||
console.log("resizing");
|
||||
let imageDatas = [];
|
||||
let leftOffset = 0;
|
||||
let topOffset = 0;
|
||||
@ -314,6 +325,368 @@ class File {
|
||||
this.currentPivotObject = event.target;
|
||||
this.currentPivotObject.classList.add("rc-selected-pivot");
|
||||
}
|
||||
|
||||
/** Opens the sprite resizing window
|
||||
*
|
||||
*/
|
||||
openResizeSpriteWindow() {
|
||||
// Inits the sprie resize inputs
|
||||
this.initResizeSpriteInputs();
|
||||
|
||||
// Computing the current ratio
|
||||
this.currentRatio = currFile.canvasSize[0] / currFile.canvasSize[1];
|
||||
|
||||
// Initializing the input fields
|
||||
this.data.width = currFile.canvasSize[0];
|
||||
this.data.height = currFile.canvasSize[1];
|
||||
|
||||
this.startData.width = parseInt(this.data.width);
|
||||
this.startData.height = parseInt(this.data.height);
|
||||
this.startData.heightPercentage = 100;
|
||||
this.startData.widthPercentage = 100;
|
||||
|
||||
// Opening the pop up now that it's ready
|
||||
Dialogue.showDialogue('resize-sprite');
|
||||
}
|
||||
|
||||
/** Initalizes the input values and binds the elements to their events
|
||||
*
|
||||
*/
|
||||
initResizeSpriteInputs() {
|
||||
document.getElementById("rs-width").value = currFile.canvasSize[0];
|
||||
document.getElementById("rs-height").value = currFile.canvasSize[1];
|
||||
|
||||
document.getElementById("rs-width-percentage").value = 100;
|
||||
document.getElementById("rs-height-percentage").value = 100;
|
||||
|
||||
document.getElementById("rs-keep-ratio").checked = true;
|
||||
|
||||
Events.on("change", "rs-width", this.changedWidth.bind(this));
|
||||
Events.on("change", "rs-height", this.changedHeight.bind(this));
|
||||
|
||||
Events.on("change", "rs-width-percentage", this.changedWidthPercentage.bind(this));
|
||||
Events.on("change", "rs-height-percentage", this.changedHeightPercentage.bind(this));
|
||||
|
||||
Events.on("click", "resize-sprite-confirm", this.resizeSprite.bind(this));
|
||||
Events.on("click", "rs-keep-ratio", this.toggleRatio.bind(this));
|
||||
Events.on("change", "resize-algorithm-combobox", this.changedAlgorithm.bind(this));
|
||||
}
|
||||
|
||||
/** Resizes (scales) the sprite
|
||||
*
|
||||
* @param {*} event
|
||||
* @param {*} ratio Keeps infos about the x ratio and y ratio
|
||||
*/
|
||||
resizeSprite(event, ratio) {
|
||||
// Old data
|
||||
let oldWidth, oldHeight;
|
||||
// New data
|
||||
let newWidth, newHeight;
|
||||
// Current imageDatas
|
||||
let rsImageDatas = [];
|
||||
// Index that will be used a few lines below
|
||||
let layerIndex = 0;
|
||||
// Copy of the imageDatas that will be stored in the history
|
||||
let imageDatasCopy = [];
|
||||
|
||||
oldWidth = currFile.canvasSize[0];
|
||||
oldHeight = currFile.canvasSize[1];
|
||||
this.rcPivot = "middle";
|
||||
|
||||
// Updating values if the user didn't press enter
|
||||
switch (document.activeElement.id) {
|
||||
case "rs-width-percentage":
|
||||
this.changedWidthPercentage();
|
||||
break;
|
||||
case "rs-width":
|
||||
this.changedWidth();
|
||||
break;
|
||||
case "rs-height-percentage":
|
||||
this.changedHeightPercentage();
|
||||
break;
|
||||
case "rs-height":
|
||||
this.changedHeight();
|
||||
break;
|
||||
default:
|
||||
// In this case everything has been updated correctly
|
||||
break;
|
||||
}
|
||||
|
||||
// Computing newWidth and newHeight
|
||||
if (ratio == null) {
|
||||
newWidth = this.data.width;
|
||||
newHeight = this.data.height;
|
||||
}
|
||||
else {
|
||||
newWidth = currFile.canvasSize[0] * ratio[0];
|
||||
newHeight = currFile.canvasSize[1] * ratio[1];
|
||||
}
|
||||
|
||||
// Get all the image datas
|
||||
for (let i=0; i<currFile.layers.length; i++) {
|
||||
if (currFile.layers[i].hasCanvas()) {
|
||||
rsImageDatas.push(currFile.layers[i].context.getImageData(
|
||||
0, 0, currFile.canvasSize[0], currFile.canvasSize[1])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// event is null when the user is undoing
|
||||
if (event != null) {
|
||||
// Copying the image data
|
||||
imageDatasCopy = rsImageDatas.slice();
|
||||
// Saving the history
|
||||
new HistoryState().ResizeSprite(newWidth / oldWidth, newHeight / oldHeight, this.currentAlgo, imageDatasCopy);
|
||||
}
|
||||
|
||||
// Resizing the canvas
|
||||
currFile.resizeCanvas(null, {x: newWidth, y: newHeight});
|
||||
|
||||
// Put the image datas on the new canvases
|
||||
for (let i=0; i<currFile.layers.length; i++) {
|
||||
if (currFile.layers[i].hasCanvas()) {
|
||||
currFile.layers[i].context.putImageData(
|
||||
this.resizeImageData(rsImageDatas[layerIndex], newWidth, newHeight, this.currentAlgo), 0, 0
|
||||
);
|
||||
currFile.layers[i].updateLayerPreview();
|
||||
layerIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// Updating start values when I finish scaling the sprite
|
||||
// OPTIMIZABLE? Can't I just assign data to startData? Is js smart enough to understand?
|
||||
if (ratio == null) {
|
||||
this.startData.width = this.data.width;
|
||||
this.startData.height = this.data.height;
|
||||
}
|
||||
else {
|
||||
this.startData.width = currFile.canvasSize[0];
|
||||
this.startData.height = currFile.canvasSize[1];
|
||||
}
|
||||
|
||||
this.startData.widthPercentage = 100;
|
||||
this.startData.heightPercentage = 100;
|
||||
|
||||
Dialogue.closeDialogue();
|
||||
}
|
||||
|
||||
/* 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
|
||||
*/
|
||||
changedWidth(event) {
|
||||
let newHeight, newHeightPerc, newWidthPerc;
|
||||
this.data.width = event.target.value;
|
||||
|
||||
newHeight = this.data.width / this.currentRatio;
|
||||
newHeightPerc = (newHeight * 100) / this.startData.height;
|
||||
newWidthPerc = (this.data.width * 100) / this.startData.width;
|
||||
|
||||
if (this.keepRatio) {
|
||||
document.getElementById("rs-height").value = newHeight;
|
||||
this.data.height = newHeight;
|
||||
|
||||
document.getElementById("rs-height-percentage").value = newHeightPerc;
|
||||
this.data.heightPercentage = newHeightPerc;
|
||||
}
|
||||
|
||||
document.getElementById("rs-width-percentage").value = newWidthPerc;
|
||||
}
|
||||
|
||||
/**Fired when the input field for width is changed. Updates the other input fields consequently
|
||||
*
|
||||
* @param {*} event
|
||||
*/
|
||||
changedHeight(event) {
|
||||
let newWidth, newWidthPerc, newHeightPerc;
|
||||
this.data.height = event.target.value;
|
||||
|
||||
newWidth = this.data.height * this.currentRatio;
|
||||
newWidthPerc = (newWidth * 100) / this.startData.width;
|
||||
newHeightPerc = (this.data.height * 100) / this.startData.height;
|
||||
|
||||
if (this.keepRatio) {
|
||||
document.getElementById("rs-width").value = newWidth;
|
||||
this.data.width = newWidth;
|
||||
|
||||
document.getElementById("rs-width-percentage").value = newWidthPerc;
|
||||
this.data.widthPercentage = newWidthPerc;
|
||||
}
|
||||
|
||||
document.getElementById("rs-height-percentage").value = newHeightPerc;
|
||||
this.data.heightPercentage = newHeightPerc;
|
||||
}
|
||||
|
||||
/**Fired when the input field for width percentage is changed. Updates the other input fields consequently
|
||||
*
|
||||
* @param {*} event
|
||||
*/
|
||||
changedWidthPercentage(event) {
|
||||
let oldValue = 100;
|
||||
let ratio;
|
||||
let newWidth, newHeight, newHeightPerc;
|
||||
|
||||
this.data.widthPercentage = event.target.value;
|
||||
ratio = this.data.widthPercentage / oldValue;
|
||||
|
||||
newHeight = this.startData.height * ratio;
|
||||
newHeightPerc = this.data.widthPercentage;
|
||||
newWidth = this.startData.width * ratio;
|
||||
|
||||
if (this.keepRatio) {
|
||||
document.getElementById("rs-height-percentage").value = newHeightPerc;
|
||||
this.data.heightPercentage = newHeightPerc;
|
||||
|
||||
document.getElementById("rs-height").value = newHeight
|
||||
this.data.height = newHeight;
|
||||
}
|
||||
|
||||
document.getElementById("rs-width").value = newWidth;
|
||||
this.data.width = newWidth;
|
||||
}
|
||||
|
||||
/**Fired when the input field for height percentage is changed. Updates the other input fields consequently
|
||||
*
|
||||
* @param {*} event
|
||||
*/
|
||||
changedHeightPercentage(event) {
|
||||
let oldValue = this.data.heightPercentage;
|
||||
let ratio;
|
||||
let newHeight, newWidth, newWidthPerc;
|
||||
|
||||
this.data.heightPercentage = event.target.value;
|
||||
|
||||
ratio = this.data.heightPercentage / oldValue;
|
||||
|
||||
newWidth = this.startData.width * ratio;
|
||||
newWidthPerc = this.data.heightPercentage;
|
||||
newHeight = this.startData.height * ratio;
|
||||
|
||||
if (this.keepRatio) {
|
||||
document.getElementById("rs-width-percentage").value = this.data.heightPercentage * currentRatio;
|
||||
this.data.widthPercentage = newWidthPerc;
|
||||
|
||||
document.getElementById("rs-width").value = newWidth;
|
||||
this.data.width = newWidth;
|
||||
}
|
||||
|
||||
document.getElementById("rs-height").value = newHeight;
|
||||
this.data.height = newHeight;
|
||||
}
|
||||
|
||||
/** Toggles the keepRatio value (fired by the checkbox in the pop up window)
|
||||
*/
|
||||
toggleRatio() {
|
||||
this.keepRatio = !this.keepRatio;
|
||||
}
|
||||
|
||||
/** Changes the scaling algorithm (fired by the combobox in the pop up window)
|
||||
*
|
||||
* @param {*} event
|
||||
*/
|
||||
changedAlgorithm(event) {
|
||||
this.currentAlgo = event.target.value;
|
||||
}
|
||||
|
||||
/** 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
|
||||
*/
|
||||
resizeImageData (image, width, height, algorithm) {
|
||||
algorithm = algorithm || 'bilinear-interpolation'
|
||||
|
||||
let resize;
|
||||
switch (algorithm) {
|
||||
case 'nearest-neighbor': resize = this.nearestNeighbor; break
|
||||
case 'bilinear-interpolation': resize = this.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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let currFile = new File();
|
@ -20,9 +20,7 @@ const FileManager = (() => {
|
||||
}
|
||||
|
||||
Util.setValue('lpe-file-name', fileName);
|
||||
|
||||
Events.on("click", "save-project-confirm", saveProject);
|
||||
|
||||
Dialogue.showDialogue('save-project', false);
|
||||
}
|
||||
|
||||
@ -38,9 +36,7 @@ const FileManager = (() => {
|
||||
}
|
||||
|
||||
Util.setValue('export-file-name', fileName);
|
||||
|
||||
Events.on("click", "export-confirm", exportProject);
|
||||
|
||||
Dialogue.showDialogue('export', false);
|
||||
}
|
||||
|
||||
@ -66,11 +62,11 @@ const FileManager = (() => {
|
||||
//create name
|
||||
let fileName = Util.getValue("export-file-name");
|
||||
//set download link
|
||||
var linkHolder = document.getElementById('save-image-link-holder');
|
||||
let linkHolder = document.getElementById('save-image-link-holder');
|
||||
// Creating a tmp canvas to flatten everything
|
||||
var exportCanvas = document.createElement("canvas");
|
||||
var emptyCanvas = document.createElement("canvas");
|
||||
var layersCopy = currFile.layers.slice();
|
||||
let exportCanvas = document.createElement("canvas");
|
||||
let emptyCanvas = document.createElement("canvas");
|
||||
let layersCopy = currFile.layers.slice();
|
||||
|
||||
exportCanvas.width = currFile.canvasSize[0];
|
||||
exportCanvas.height = currFile.canvasSize[1];
|
||||
|
@ -104,8 +104,8 @@ class HistoryState {
|
||||
this.undo = function() {
|
||||
let layerIndex = 0;
|
||||
|
||||
currentAlgo = algo;
|
||||
resizeSprite(null, [1 / this.xRatio, 1 / this.yRatio]);
|
||||
currFile.currentAlgo = algo;
|
||||
currFile.resizeSprite(null, [1 / this.xRatio, 1 / this.yRatio]);
|
||||
|
||||
// Also putting the old data
|
||||
for (let i=0; i<currFile.layers.length; i++) {
|
||||
@ -118,8 +118,8 @@ class HistoryState {
|
||||
};
|
||||
|
||||
this.redo = function() {
|
||||
currentAlgo = algo;
|
||||
resizeSprite(null, [this.xRatio, this.yRatio]);
|
||||
currFile.currentAlgo = algo;
|
||||
currFile.resizeSprite(null, [this.xRatio, this.yRatio]);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
const Startup = (() => {
|
||||
|
||||
let firstPixel = true;
|
||||
let editorMode = "Basic";
|
||||
let splashPostfix = '';
|
||||
|
||||
Events.on('click', 'create-button', create, false);
|
||||
@ -142,7 +141,7 @@ const Startup = (() => {
|
||||
else {
|
||||
//if this palette isnt the one specified in the url, then reset the url
|
||||
if (!palettes[selectedPalette].specified)
|
||||
history.pushState(null, null, '/pixel-editor');
|
||||
history.pushState(null, null, '/pixel-editor');
|
||||
|
||||
//fill the palette with specified colours
|
||||
ColorModule.createColorPalette(palettes[selectedPalette].colors);
|
||||
|
@ -1,396 +0,0 @@
|
||||
// REFACTOR: method of File class probably
|
||||
|
||||
/* This scripts contains all the code used to handle the sprite scaling */
|
||||
// Should I keep the sprite ratio?
|
||||
let keepRatio = true;
|
||||
// Used to store the current ratio
|
||||
let currentRatio;
|
||||
// The currenty selected resizing algorithm (nearest-neighbor or bilinear-interpolation)
|
||||
let currentAlgo = 'nearest-neighbor';
|
||||
// Current resize data
|
||||
let data = {width: 0, height: 0, widthPercentage: 100, heightPercentage: 100};
|
||||
// Start resize data
|
||||
let startData = {width: 0, height:0, widthPercentage: 100, heightPercentage: 100};
|
||||
|
||||
/** Opens the sprite resizing window
|
||||
*
|
||||
*/
|
||||
function openResizeSpriteWindow() {
|
||||
// Inits the sprie resize inputs
|
||||
initResizeSpriteInputs();
|
||||
|
||||
// Computing the current ratio
|
||||
currentRatio = currFile.canvasSize[0] / currFile.canvasSize[1];
|
||||
|
||||
console.log("Current ratio: " + currentRatio);
|
||||
|
||||
// Initializing the input fields
|
||||
data.width = currFile.canvasSize[0];
|
||||
data.height = currFile.canvasSize[1];
|
||||
|
||||
startData.width = parseInt(data.width);
|
||||
startData.height = parseInt(data.height);
|
||||
startData.heightPercentage = 100;
|
||||
startData.widthPercentage = 100;
|
||||
|
||||
// Opening the pop up now that it's ready
|
||||
Dialogue.showDialogue('resize-sprite');
|
||||
}
|
||||
|
||||
/** Initalizes the input values and binds the elements to their events
|
||||
*
|
||||
*/
|
||||
function initResizeSpriteInputs() {
|
||||
document.getElementById("rs-width").value = currFile.canvasSize[0];
|
||||
document.getElementById("rs-height").value = currFile.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);
|
||||
document.getElementById("resize-algorithm-combobox").addEventListener("change", changedAlgorithm);
|
||||
}
|
||||
|
||||
/** Resizes (scales) the sprite
|
||||
*
|
||||
* @param {*} event
|
||||
* @param {*} ratio Keeps infos about the x ratio and y ratio
|
||||
*/
|
||||
function resizeSprite(event, ratio) {
|
||||
// Old data
|
||||
let oldWidth, oldHeight;
|
||||
// New data
|
||||
let newWidth, newHeight;
|
||||
// Current imageDatas
|
||||
let rsImageDatas = [];
|
||||
// Index that will be used a few lines below
|
||||
let layerIndex = 0;
|
||||
// Copy of the imageDatas that will be stored in the history
|
||||
let imageDatasCopy = [];
|
||||
|
||||
oldWidth = currFile.canvasSize[0];
|
||||
oldHeight = currFile.canvasSize[1];
|
||||
rcPivot = "middle";
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Computing newWidth and newHeight
|
||||
if (ratio == null) {
|
||||
newWidth = data.width;
|
||||
newHeight = data.height;
|
||||
}
|
||||
else {
|
||||
newWidth = currFile.canvasSize[0] * ratio[0];
|
||||
newHeight = currFile.canvasSize[1] * ratio[1];
|
||||
}
|
||||
|
||||
// Get all the image datas
|
||||
for (let i=0; i<currFile.layers.length; i++) {
|
||||
if (currFile.layers[i].hasCanvas()) {
|
||||
rsImageDatas.push(currFile.layers[i].context.getImageData(
|
||||
0, 0, currFile.canvasSize[0], currFile.canvasSize[1])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// event is null when the user is undoing
|
||||
if (event != null) {
|
||||
// Copying the image data
|
||||
imageDatasCopy = rsImageDatas.slice();
|
||||
// Saving the history
|
||||
new HistoryState().ResizeSprite(newWidth / oldWidth, newHeight / oldHeight, currentAlgo, imageDatasCopy);
|
||||
}
|
||||
|
||||
// Resizing the canvas
|
||||
currFile.resizeCanvas(null, {x: newWidth, y: newHeight});
|
||||
|
||||
// Put the image datas on the new canvases
|
||||
for (let i=0; i<currFile.layers.length; i++) {
|
||||
if (currFile.layers[i].hasCanvas()) {
|
||||
currFile.layers[i].context.putImageData(
|
||||
resizeImageData(rsImageDatas[layerIndex], newWidth, newHeight, currentAlgo), 0, 0
|
||||
);
|
||||
currFile.layers[i].updateLayerPreview();
|
||||
layerIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// Updating start values when I finish scaling the sprite
|
||||
// OPTIMIZABLE? Can't I just assign data to startData? Is js smart enough to understand?
|
||||
if (ratio == null) {
|
||||
startData.width = data.width;
|
||||
startData.height = data.height;
|
||||
}
|
||||
else {
|
||||
startData.width = currFile.canvasSize[0];
|
||||
startData.height = currFile.canvasSize[1];
|
||||
}
|
||||
|
||||
startData.widthPercentage = 100;
|
||||
startData.heightPercentage = 100;
|
||||
|
||||
Dialogue.closeDialogue();
|
||||
}
|
||||
|
||||
/* 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
|
||||
*/
|
||||
function changedWidth(event) {
|
||||
let oldValue = data.width;
|
||||
let ratio;
|
||||
let percentageRatio;
|
||||
let newHeight, newHeightPerc, newWidthPerc;
|
||||
|
||||
data.width = event.target.value;
|
||||
delta = data.width - oldValue;
|
||||
|
||||
ratio = data.width / oldValue;
|
||||
|
||||
newHeight = data.width / currentRatio;
|
||||
newHeightPerc = (newHeight * 100) / startData.height;
|
||||
newWidthPerc = (data.width * 100) / startData.width;
|
||||
|
||||
if (keepRatio) {
|
||||
document.getElementById("rs-height").value = newHeight;
|
||||
data.height = newHeight;
|
||||
|
||||
document.getElementById("rs-height-percentage").value = newHeightPerc;
|
||||
data.heightPercentage = newHeightPerc;
|
||||
}
|
||||
|
||||
document.getElementById("rs-width-percentage").value = newWidthPerc;
|
||||
}
|
||||
|
||||
/**Fired when the input field for width is changed. Updates the other input fields consequently
|
||||
*
|
||||
* @param {*} event
|
||||
*/
|
||||
function changedHeight(event) {
|
||||
let oldValue = 100;
|
||||
let ratio;
|
||||
let newWidth, newWidthPerc, newHeightPerc;
|
||||
|
||||
data.height = event.target.value;
|
||||
delta = data.height - oldValue;
|
||||
|
||||
ratio = data.height / oldValue;
|
||||
|
||||
newWidth = data.height * currentRatio;
|
||||
newWidthPerc = (newWidth * 100) / startData.width;
|
||||
newHeightPerc = (data.height * 100) / startData.height;
|
||||
|
||||
if (keepRatio) {
|
||||
document.getElementById("rs-width").value = newWidth;
|
||||
data.width = newWidth;
|
||||
|
||||
document.getElementById("rs-width-percentage").value = newWidthPerc;
|
||||
data.widthPercentage = newWidthPerc;
|
||||
}
|
||||
|
||||
document.getElementById("rs-height-percentage").value = newHeightPerc;
|
||||
data.heightPercentage = newHeightPerc;
|
||||
}
|
||||
|
||||
/**Fired when the input field for width percentage is changed. Updates the other input fields consequently
|
||||
*
|
||||
* @param {*} event
|
||||
*/
|
||||
function changedWidthPercentage(event) {
|
||||
let oldValue = 100;
|
||||
let ratio;
|
||||
let newWidth, newHeight, newHeightPerc;
|
||||
|
||||
data.widthPercentage = event.target.value;
|
||||
delta = data.widthPercentage - oldValue;
|
||||
|
||||
ratio = data.widthPercentage / oldValue;
|
||||
|
||||
console.log("old value: " + oldValue + ", ratio: " + ratio);
|
||||
|
||||
newHeight = startData.height * ratio;
|
||||
newHeightPerc = data.widthPercentage;
|
||||
newWidth = startData.width * ratio;
|
||||
|
||||
if (keepRatio) {
|
||||
document.getElementById("rs-height-percentage").value = newHeightPerc;
|
||||
data.heightPercentage = newHeightPerc;
|
||||
|
||||
document.getElementById("rs-height").value = newHeight
|
||||
data.height = newHeight;
|
||||
}
|
||||
|
||||
document.getElementById("rs-width").value = newWidth;
|
||||
data.width = newWidth;
|
||||
}
|
||||
|
||||
/**Fired when the input field for height percentage is changed. Updates the other input fields consequently
|
||||
*
|
||||
* @param {*} event
|
||||
*/
|
||||
function changedHeightPercentage(event) {
|
||||
let oldValue = data.heightPercentage;
|
||||
let ratio;
|
||||
let newHeight, newWidth, newWidthPerc;
|
||||
|
||||
data.heightPercentage = event.target.value;
|
||||
delta = data.heightPercentage - oldValue;
|
||||
|
||||
ratio = data.heightPercentage / oldValue;
|
||||
|
||||
newWidth = startData.width * ratio;
|
||||
newWidthPerc = data.heightPercentage;
|
||||
newHeight = startData.height * ratio;
|
||||
|
||||
if (keepRatio) {
|
||||
document.getElementById("rs-width-percentage").value = data.heightPercentage * currentRatio;
|
||||
data.widthPercentage = newWidthPerc;
|
||||
|
||||
document.getElementById("rs-width").value = newWidth;
|
||||
data.width = newWidth;
|
||||
}
|
||||
|
||||
document.getElementById("rs-height").value = newHeight;
|
||||
data.height = newHeight;
|
||||
}
|
||||
|
||||
/** Toggles the keepRatio value (fired by the checkbox in the pop up window)
|
||||
*
|
||||
* @param {*} event
|
||||
*/
|
||||
function toggleRatio(event) {
|
||||
keepRatio = !keepRatio;
|
||||
}
|
||||
|
||||
/** Changes the scaling algorithm (fired by the combobox in the pop up window)
|
||||
*
|
||||
* @param {*} event
|
||||
*/
|
||||
function changedAlgorithm(event) {
|
||||
currentAlgo = event.target.value;
|
||||
}
|
||||
|
||||
/** 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
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,17 @@
|
||||
/**utilities**/
|
||||
/**EXTERNALS AND LIBRARIES**/
|
||||
//=include lib/cookies.js
|
||||
//=include lib/jscolor.js
|
||||
//=include data/variables.js
|
||||
//=include lib/sortable.js
|
||||
|
||||
/**UTILITY AND INPUT*/
|
||||
//=include Util.js
|
||||
//=include Events.js
|
||||
//=include Color.js
|
||||
//=include Dialogue.js
|
||||
//=include History.js
|
||||
|
||||
//=include Color.js
|
||||
|
||||
|
||||
//=include File.js
|
||||
//=include ColorModule.js
|
||||
|
||||
@ -45,8 +48,6 @@
|
||||
//=include data/palettes.js
|
||||
|
||||
/**functions**/
|
||||
//=include _resizeCanvas.js
|
||||
//=include _resizeSprite.js
|
||||
//=include ColorPicker.js
|
||||
//=include PaletteBlock.js
|
||||
//=include SplashPage.js
|
||||
|
@ -40,7 +40,7 @@ class FillTool extends Tool {
|
||||
}
|
||||
|
||||
//temporary image holds the data while we change it
|
||||
let tempImage = currFile.currentLayer.context.getImageData(0, 0, canvasSize[0], canvasSize[1]);
|
||||
let tempImage = currFile.currentLayer.context.getImageData(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]);
|
||||
|
||||
//this is an array that holds all of the pixels at the top of the cluster
|
||||
let topmostPixelsArray = [[Math.floor(cursorLocation[0]/currFile.zoom), Math.floor(cursorLocation[1]/currFile.zoom)]];
|
||||
|
@ -69,7 +69,7 @@ class MoveSelectionTool extends Tool {
|
||||
mousePos[1]/currFile.zoom, this.currSelection.width, this.currSelection.height);
|
||||
|
||||
// clear the entire tmp layer
|
||||
TMPLayer.context.clearRect(0, 0, currFile.TMPLayer.canvas.width, currFile.TMPLayer.canvas.height);
|
||||
currFile.TMPLayer.context.clearRect(0, 0, currFile.TMPLayer.canvas.width, currFile.TMPLayer.canvas.height);
|
||||
// put the image data on the tmp layer with offset
|
||||
currFile.TMPLayer.context.putImageData(
|
||||
this.currSelection.data,
|
||||
@ -148,16 +148,10 @@ class MoveSelectionTool extends Tool {
|
||||
// If the pixel of the clipboard is empty, but the one below it isn't, I use the pixel below
|
||||
if (Util.isPixelEmpty(currentMovePixel)) {
|
||||
if (!Util.isPixelEmpty(currentUnderlyingPixel)) {
|
||||
console.log("Original data: " + this.currSelection.data.data[i] + "," +
|
||||
this.currSelection.data.data[i+1], this.currSelection.data.data[i+2]);
|
||||
|
||||
pasteData[i] = currentUnderlyingPixel[0];
|
||||
pasteData[i+1] = currentUnderlyingPixel[1];
|
||||
pasteData[i+2] = currentUnderlyingPixel[2];
|
||||
pasteData[i+3] = currentUnderlyingPixel[3];
|
||||
|
||||
console.log("After data: " + this.currSelection.data.data[i] + "," +
|
||||
this.currSelection.data.data[i+1], this.currSelection.data.data[i+2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ class RectangleTool extends ResizableTool {
|
||||
currFile.currentLayer.context.lineWidth = this.currSize;
|
||||
|
||||
// Drawing the rect using 4 lines
|
||||
currentLayer.drawLine(startRectX, startRectY, endRectX, startRectY, this.currSize);
|
||||
currFile.currentLayer.drawLine(startRectX, startRectY, endRectX, startRectY, this.currSize);
|
||||
currFile.currentLayer.drawLine(endRectX, startRectY, endRectX, endRectY, this.currSize);
|
||||
currFile.currentLayer.drawLine(endRectX, endRectY, startRectX, endRectY, this.currSize);
|
||||
currFile.currentLayer.drawLine(startRectX, endRectY, startRectX, startRectY, this.currSize);
|
||||
|
12
refactor_dependencies
Normal file
12
refactor_dependencies
Normal file
@ -0,0 +1,12 @@
|
||||
Input <- Startup, LayerList, TopMenuModule: Could be resolved by using custom events
|
||||
|
||||
Startup <- ColorModule, ToolManager, LayerList, EditorState, Layer(++)
|
||||
|
||||
EditorState <- LayerList, File, Startup(documentCreated): maybe documentCreated should stay in EditorState (that would fix the
|
||||
circular dependency with Startup), for the other dependencies it'd probably be nice for them to listen to a custom events
|
||||
|
||||
Color <- NONE
|
||||
|
||||
ColorModule <- File
|
||||
|
||||
File <- Startup, but only because Startup sets certain variables in File (pixelGrid, checkerboard and the other global layers)
|
@ -23,7 +23,7 @@
|
||||
<li>
|
||||
<button>View</button>
|
||||
<ul>
|
||||
<li><button id="toggle-pixelgrid-button" onclick="pixelGrid.togglePixelGrid()">Show pixel grid</button></li>
|
||||
<li><button id="toggle-pixelgrid-button" onclick="currFile.pixelGrid.togglePixelGrid()">Show pixel grid</button></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
|
Loading…
Reference in New Issue
Block a user