I have a simple program, but I have an issue. I previously had a problem with the page displaying "Loading..." before, but I fixed that. Whenever I try to load now, I get "localhost refused to connect". I have tried the internet but all the fixes seem to be for the first error. How do I get it to load? Here is the code:
let pew;
let crosshair;
let imgConst = 100;
var imgSize = imgConst;
var imgChange = [4, (6 + 2/3), 10, -10, -10, -10, -10, -20, (-33 - 1/3), -50];
for(var i = 0; i = imgChange.length; i++) {
imgChange[i] = imgSize + imgConst/imgChange[i]
console.log(i)
}
function preload() {
crosshair = loadImage('crosshair.png')
}
function setup() {
createCanvas(windowWidth, windowHeight);
frameRate(10);
noStroke();
noCursor();
pew = new bullet();
}
function draw() {
background(50, 89, 100);
//Shrink bullets
pew.shrink()
//crosshair
image(crosshair, mouseX - imgSize/2, mouseY - imgSize/2, imgSize, imgSize);
}
// bullet class
class bullet {
constructor() {
this.x = [];
this.y = [];
this.size = [];
this.shrinkSpeed = 1;
this.diameter = 10;
}
shrink() {
fill(61, 41, 15)
for(var i = 0; i < this.x.length; i++) {
if(this.size[i] <= 1) {
this.size.splice(i);
} else {
ellipse(this.x[i], this.y[i], this.size[i], this.size[i]);
this.size[i] = this.size[i] - this.shrinkSpeed;
}
}
}
add() {
this.x.push(mouseX);
this.y.push(mouseY);
this.size.push(this.diameter)
for( i = 0; i < imgChange.length * 1; i ++) {
imgSize = imgChange[floor(i)]
console.log(imgSize);
}
}
}
function mousePressed() {
//add bullet
pew.add();
}
You have 2 problems in for loops.
First, in your first for loop, change i = imgChange.length to i < imgChange.length. Second, you are missing a var in the add() function of your bullet class: for( i = 0.
Now, your code should run.
I have a simple game, I'm trying to get the shortest route between 2 points
The map consists of 2d array matrix: Node[][],
class Node{
index: {
x: number,
y: number
},
isAvailable: boolean
}
The algorithm should return the shortest path with respect to node availability.
e.g. Trees are marked as unavailable node.isAvailable = false
I'm stuck on implementing the algorithm for this matrix
I tried to use Dijkstras algorithm from here, but I couldn't figure out how to apply it, I did
const graph = new Dijkstra();
//convert the matrix (2d array) to graph
matrix.map((row) => {
row.map((node: Node) => {
let x = node.index.x;
let y = node.index.y;
graph.addVertex(x + ":" + y, {x: x, y: y});
});
});
console.log(graph.shortestPath('0:0', '5:5'));
//the output was ['0:0'] (definitly not the answer)
How can I apply the algorithm on this matrix?
P.S here is my full code
I used a method best described as spilling paint at the target:
You mark the target square with 0, then traverse the neighbours and mark them as 1, which represents distance to target, then traverse neighbours of neighbours, etc. Repeat the process until the paint reaches your troll. All that is left for troll to do is just start moving to the squares with lowest potential.
It becomes more fun once you have multiple characters that need to path around each other while everyone is moving.
I had to implement the A* algorithm
export class PathFinder {
grid: Tile[][];
gridHeight: number;
gridWidth: number;
startTile: Tile;
endTile: Tile;
/** Array of the already checked tiles. */
closedList: List<Tile> = new List<Tile>();
openList: List<Tile> = new List<Tile>();
constructor(grid: Tile[][], gridHeight: number, gridWidth: number) {
this.grid = grid;
this.gridHeight = gridHeight;
this.gridWidth = gridWidth;
}
searchPath(start: Tile, end: Tile): Tile[] {
this.startTile = start;
this.endTile = end;
/** Path validation */
if (!start.walkable) {
console.log('The start tile in not walkable, choose different tile than', start.index);
return [];
}
if (!end.walkable) {
console.log('The end tile in not walkable, choose different tile than', end.index);
return [];
}
/** Start A* Algorithm */
/** Add the starting tile to the openList */
this.openList.push(start);
let currentTile;
/** While openList is not empty */
while (this.openList.length) {
//current node = node for open list with the lowest cost.
currentTile = this.getTileWithLowestTotal(this.openList);
//if the currentTile is the endTile, then we can stop searching
if(JSON.stringify(currentTile.index) === JSON.stringify(end.index)){
this.startTile.setBackgroundColor("rgba(255, 45, 45, .8)");
this.endTile.setBackgroundColor("rgba(255, 45, 45, .8)");
return this.shortestPath();
}
else {
//move the current tile to the closed list and remove it from the open list.
this.openList.remove(currentTile);
this.closedList.push(currentTile);
// //Get all adjacent Tiles
let adjacentTiles = this.getAdjacentTiles(currentTile);
for (let adjacentTile of adjacentTiles) {
//Get tile is not in the open list
if (!this.openList.contains(adjacentTile)) {
//Get tile is not in the closed list
if (!this.closedList.contains(adjacentTile)) {
//move it to the open list and calculate cost
this.openList.push(adjacentTile);
//calculate the cost
adjacentTile.cost = currentTile.cost + 1;
//calculate the manhattan distance
adjacentTile.heuristic = this.manhattanDistance(adjacentTile);
// calculate the total amount
adjacentTile.total = adjacentTile.cost + adjacentTile.heuristic;
currentTile.setBackgroundColor('rgba(0, 181, 93, 0.8)');
}
}
}
}
}
}
getTileWithLowestTotal(openList: Tile[]): Tile {
let tileWithLowestTotal = new Tile();
let lowestTotal: number = 999999999;
/** Search open tiles and get the tile with the lowest total cost */
for (let openTile of openList) {
if (openTile.total <= lowestTotal) {
//clone lowestTotal
lowestTotal = openTile.total;
tileWithLowestTotal = openTile;
}
}
return tileWithLowestTotal;
}
getAdjacentTiles(current: Tile): Tile[] {
let adjacentTiles: Tile[] = [];
let adjacentTile: Tile;
//Tile to left
if (current.index.x - 1 >= 0) {
adjacentTile = this.grid[current.index.x - 1][current.index.y];
if (adjacentTile && adjacentTile.walkable) {
adjacentTiles.push(adjacentTile);
}
}
//Tile to right
if (current.index.x + 1 < this.gridWidth) {
adjacentTile = this.grid[current.index.x + 1][current.index.y];
if (adjacentTile && adjacentTile.walkable) {
adjacentTiles.push(adjacentTile);
}
}
//Tile to Under
if (current.index.y + 1 < this.gridHeight) {
adjacentTile = this.grid[current.index.x][current.index.y + 1];
if (adjacentTile && adjacentTile.walkable) {
adjacentTiles.push(adjacentTile);
}
}
//Tile to Above
if (current.index.y - 1 >= 0) {
adjacentTile = this.grid[current.index.x][current.index.y - 1];
if (adjacentTile && adjacentTile.walkable) {
adjacentTiles.push(adjacentTile);
}
}
/** TODO: Diagonal moves */
return adjacentTiles;
}
/** Calculate the manhattan distance */
manhattanDistance(adjacentTile: Tile): number {
return Math.abs((this.endTile.index.x - adjacentTile.index.x) +
(this.endTile.index.y - adjacentTile.index.y));
}
shortestPath() {
let startFound: boolean = false;
let currentTile = this.endTile;
let pathTiles = [];
//includes the end tile in the path
pathTiles.push(this.endTile);
this.endTile.ball = true;
while (!startFound) {
let adjacentTiles = this.getAdjacentTiles(currentTile);
//check to see what newest current tile.
for (let adjacentTile of adjacentTiles) {
//check if it is the start tile
if (JSON.stringify(adjacentTile.index) === JSON.stringify(this.startTile.index)){
return pathTiles;
}
//it has to be inside the closedList or openList
if (this.closedList.contains(adjacentTile) || this.openList.contains(adjacentTile)) {
if (adjacentTile.cost <= currentTile.cost && adjacentTile.cost > 0) {
//change the current tile.
currentTile = adjacentTile;
//Add this adjacentTile to the path list
pathTiles.push(adjacentTile);
//highlight way with yellow balls
adjacentTile.ball = true;
break;
}
}
}
}
}
}
I'm referring to the official example on Phaser.io site, but have copied it here for reference below. What I want, and repeatedly fail to achieve is that the moving (with keyboard keys) starfield sprite would not collide with other vegies sprites.
I did go through the docs and looked here on SO and their forum, and it seems that the solutions should be easy enough; to just put the following code in the update() function:
game.world.bringToTop(sprite);
But, for some reason this is not working for me, so please tell me what I'm doing wrong.
var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update });
function preload() {
game.load.image('sky', 'assets/skies/sky4.png');
game.load.image('starfield', 'assets/misc/starfield.jpg');
game.load.spritesheet('veggies', 'assets/sprites/fruitnveg64wh37.png', 64, 64);
}
var sprite;
var cursors;
var veggies;
function create() {
game.add.image(0, 0, 'sky');
// Enable p2 physics
game.physics.startSystem(Phaser.Physics.P2JS);
// Make things a bit more bouncey
game.physics.p2.defaultRestitution = 0.8;
// Add a sprite
sprite = game.add.tileSprite(300, 450, 200, 50, 'starfield');
// Enable if for physics. This creates a default rectangular body.
game.physics.p2.enable(sprite);
veggies = game.add.group();
veggies.enableBody = true;
veggies.physicsBodyType = Phaser.Physics.P2JS;
var vegFrames = [ 1, 3, 4, 8 ];
for (var i = 0; i < 10; i++)
{
var veg = veggies.create(game.world.randomX, game.world.randomY, 'veggies', game.rnd.pick(vegFrames));
veg.body.setCircle(26);
}
text = game.add.text(20, 20, 'move with arrow keys', { fill: '#ffffff' });
cursors = game.input.keyboard.createCursorKeys();
}
function update() {
sprite.body.setZeroVelocity();
game.world.bringToTop(veggies);
if (cursors.left.isDown)
{
sprite.body.moveLeft(400);
sprite.tilePosition.x -= 8;
}
else if (cursors.right.isDown)
{
sprite.body.moveRight(400);
sprite.tilePosition.x += 8;
}
if (cursors.up.isDown)
{
sprite.body.moveUp(400);
sprite.tilePosition.y -= 8;
}
else if (cursors.down.isDown)
{
sprite.body.moveDown(400);
sprite.tilePosition.y += 8;
}
}
edit: Solution which worked in the end thanks to SirSandman's answer:
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'phaser-example', { preload: preload, create: create, update: update, render: render });
function preload() {
game.load.image('stars', 'assets/misc/starfield.jpg');
game.load.spritesheet('ship', 'assets/sprites/humstar.png', 32, 32);
game.load.image('panda', 'assets/sprites/spinObj_01.png');
game.load.image('sweet', 'assets/sprites/spinObj_06.png');
}
var ship;
var starfield;
var cursors;
function create() {
// Enable P2
game.physics.startSystem(Phaser.Physics.P2JS);
// Turn on impact events for the world, without this we get no collision callbacks
game.physics.p2.setImpactEvents(true);
game.physics.p2.restitution = 0.8;
// Create our collision groups. One for the player, one for the pandas
var playerCollisionGroup = game.physics.p2.createCollisionGroup();
var pandaCollisionGroup = game.physics.p2.createCollisionGroup();
// This part is vital if you want the objects with their own collision groups to still collide with the world bounds
// (which we do) - what this does is adjust the bounds to use its own collision group.
game.physics.p2.updateBoundsCollisionGroup();
starfield = game.add.tileSprite(0, 0, 800, 600, 'stars');
starfield.fixedToCamera = true;
var pandas = game.add.group();
pandas.enableBody = true;
pandas.physicsBodyType = Phaser.Physics.P2JS;
for (var i = 0; i < 4; i++)
{
var panda = pandas.create(game.world.randomX, game.world.randomY, 'panda');
panda.body.setRectangle(40, 40);
// Tell the panda to use the pandaCollisionGroup
panda.body.setCollisionGroup(pandaCollisionGroup);
// Pandas will collide against themselves and the player
// If you don't set this they'll not collide with anything.
// The first parameter is either an array or a single collision group.
panda.body.collides(pandaCollisionGroup);
panda.body.velocity.x = 500;
panda.body.velocity.y = 500;
}
// Create our ship sprite
ship = game.add.sprite(200, 200, 'ship');
ship.scale.set(2);
ship.smoothed = false;
ship.animations.add('fly', [0,1,2,3,4,5], 10, true);
ship.play('fly');
game.physics.p2.enable(ship, false);
ship.body.setCircle(28);
ship.body.fixedRotation = true;
// Set the ships collision group
ship.body.setCollisionGroup(playerCollisionGroup);
// The ship will collide with the pandas, and when it strikes one the hitPanda callback will fire, causing it to alpha out a bit
// When pandas collide with each other, nothing happens to them.
game.camera.follow(ship);
cursors = game.input.keyboard.createCursorKeys();
}
function hitPanda(body1, body2) {
// body1 is the space ship (as it's the body that owns the callback)
// body2 is the body it impacted with, in this case our panda
// As body2 is a Phaser.Physics.P2.Body object, you access its own (the sprite) via the sprite property:
body2.sprite.alpha -= 0.1;
}
function update() {
ship.body.setZeroVelocity();
if (cursors.left.isDown)
{
ship.body.moveLeft(200);
}
else if (cursors.right.isDown)
{
ship.body.moveRight(200);
}
if (cursors.up.isDown)
{
ship.body.moveUp(200);
}
else if (cursors.down.isDown)
{
ship.body.moveDown(200);
}
if (!game.camera.atLimit.x)
{
starfield.tilePosition.x += (ship.body.velocity.x * 16) * game.time.physicsElapsed;
}
if (!game.camera.atLimit.y)
{
starfield.tilePosition.y += (ship.body.velocity.y * 16) * game.time.physicsElapsed;
}
}
function render() {
game.debug.text('Collide with the Pandas!', 32, 32);
}
I P2 you have to set the Collisiongroups in contrast to arcarde.
I think you have to set a collisiongroup for the sprite like that:
var veggCollisionGroup = game.physics.p2.createCollisionGroup();
and then define with which other groups this group shell collide like that in the Loop:
veggies.body.setCollisionGroup(veggCollisionGroup);
veggies.body.collides(veggCollisionGroup);
And then the your tilesprite should collide with your other tilesprites.
Source:
http://phaser.io/examples/v2/p2-physics/collision-groups
if i should be wrong you will find your answer in the examples. :)
Is there any built-in support for for undo/redo in Fabric.js? Can you please guide me on how you used this cancel and repeat in [http://printio.ru/][1]
In http://jsfiddle.net/SpgGV/9/, move the object and change its size. If the object state is changed, and then we do undo/redo, its previous state will be deleted when the next change comes. It makes it easier to do undo/redo. All events of canvas should be called before any element is added to canvas. I didn't add an object:remove event here. You can add it yourself. If one element is removed, the state and list should be invalid if this element is in this array. The simpler way is to set state and list = [] and index = 0.
This will clear the state of your undo/redo queue. If you want to keep all states, such as add/remove, my suggestion is to add more properties to the element of your state array. For instance, state = [{"data":object.originalState, "event": "added"}, ....]. The "event" could be "modified" or "added" and set in a corresponding event handler.
If you have added one object, then set state[index].event="added" so that next time, when you use undo, you check it. If it's "added", then remove it anyway. Or when you use redo, if the target one is "added", then you added it. I've recently been quite busy. I will add codes to jsfiddle.net later.
Update: added setCoords() ;
var current;
var list = [];
var state = [];
var index = 0;
var index2 = 0;
var action = false;
var refresh = true;
canvas.on("object:added", function (e) {
var object = e.target;
console.log('object:modified');
if (action === true) {
state = [state[index2]];
list = [list[index2]];
action = false;
console.log(state);
index = 1;
}
object.saveState();
console.log(object.originalState);
state[index] = JSON.stringify(object.originalState);
list[index] = object;
index++;
index2 = index - 1;
refresh = true;
});
canvas.on("object:modified", function (e) {
var object = e.target;
console.log('object:modified');
if (action === true) {
state = [state[index2]];
list = [list[index2]];
action = false;
console.log(state);
index = 1;
}
object.saveState();
state[index] = JSON.stringify(object.originalState);
list[index] = object;
index++;
index2 = index - 1;
console.log(state);
refresh = true;
});
function undo() {
if (index <= 0) {
index = 0;
return;
}
if (refresh === true) {
index--;
refresh = false;
}
console.log('undo');
index2 = index - 1;
current = list[index2];
current.setOptions(JSON.parse(state[index2]));
index--;
current.setCoords();
canvas.renderAll();
action = true;
}
function redo() {
action = true;
if (index >= state.length - 1) {
return;
}
console.log('redo');
index2 = index + 1;
current = list[index2];
current.setOptions(JSON.parse(state[index2]));
index++;
current.setCoords();
canvas.renderAll();
}
Update: better solution to take edit history algorithm into account. Here we can use Editing.getInst().set(item) where the item could be {action, object, state}; For example, {"add", object, "{JSON....}"}.
/**
* Editing : we will save element states into an queue, and the length of queue
* is fixed amount, for example, 0..99, each element will be insert into the top
* of queue, queue.push, and when the queue is full, we will shift the queue,
* to remove the oldest element from the queue, queue.shift, and then we will
* do push.
*
* So the latest state will be at the top of queue, and the oldest one will be
* at the bottom of the queue (0), and the top of queue is changed, could be
* 1..99.
*
* The initialized action is "set", it will insert item into the top of queue,
* even if it arrived the length of queue, it will queue.shift, but still do
* the same thing, and queue only abandon the oldest element this time. When
* the current is changed and new state is coming, then this time, top will be
* current + 1.
*
* The prev action is to fetch "previous state" of the element, and it will use
* "current" to do this job, first, we will --current, and then we will return
* the item of it, because "current" always represent the "current state" of
* element. When the current is equal 0, that means, we have fetched the last
* element of the queue, and then it arrived at the bottom of the queue.
*
* The next action is to fetch "next state" after current element, and it will
* use "current++" to do the job, when the current is equal to "top", it means
* we have fetched the latest element, so we should stop.
*
* If the action changed from prev/next to "set", then we should reset top to
* "current", and abandon all rest after that...
*
* Here we should know that, if we keep the reference in the queue, the item
* in the queue will never be released.
*
*
* #constructor
*/
function Editing() {
this.queue = [];
this.length = 4;
this.bottom = 0;
this.top = 0;
this.current = 0;
this.empty = true;
// At the Begin of Queue
this.BOQ = true;
// At the End of Queue
this.EOQ = true;
// 0: set, 1: prev, 2: next
this._action = 0;
this._round = 0;
}
Editing.sharedInst = null;
Editing.getInst = function (owner) {
if (Editing.sharedInst === null) {
Editing.sharedInst = new Editing(owner);
}
return Editing.sharedInst;
};
/**
* To set the item into the editing queue, and mark the EOQ, BOQ, so we know
* the current position.
*
* #param item
*/
Editing.prototype.set = function (item) {
console.log("=== Editing.set");
var result = null;
if (this._action != 0) {
this.top = this.current + 1;
}
if (this.top >= this.length) {
result = this.queue.shift();
this.top = this.length - 1;
}
this._action = 0;
this.queue[this.top] = item;
this.current = this.top;
this.top++;
this.empty = false;
this.EOQ = true;
this.BOQ = false;
console.log("==> INFO : ");
console.log(item);
console.log("===========");
console.log("current: ", 0 + this.current);
console.log("start: ", 0 + this.bottom);
console.log("end: ", 0 + this.top);
return result;
};
/**
* To fetch the previous item just before current one
*
* #returns {item|boolean}
*/
Editing.prototype.prev = function () {
console.log("=== Editing.prev");
if (this.empty) {
return false;
}
if (this.BOQ) {
return false;
}
this._action = 1;
this.current--;
if (this.current == this.bottom) {
this.BOQ = true;
}
var item = this.queue[this.current];
this.EOQ = false;
console.log("==> INFO : ");
console.log(item);
console.log("===========");
console.log("current: ", 0 + this.current);
console.log("start: ", 0 + this.bottom);
console.log("end: ", 0 + this.top);
return item;
};
/**
* To fetch the next item just after the current one
*
* #returns {*|boolean}
*/
Editing.prototype.next = function () {
console.log("=== Editing.next");
if (this.empty) {
return false;
}
if (this.EOQ) {
return false;
}
this.current++;
if (this.current == this.top - 1 && this.top < this.length) {
this.EOQ = true;
}
if (this.current == this.top - 1 && this.top == this.length) {
this.EOQ = true;
}
this._action = 2;
var item = this.queue[this.current];
this.BOQ = false;
console.log("==> INFO : ");
console.log(item);
console.log("===========");
console.log("current: ", 0 + this.current);
console.log("start: ", 0 + this.bottom);
console.log("end: ", 0 + this.top);
return item;
};
/**
* To empty the editing and reset all state
*/
Editing.prototype.clear = function () {
this.queue = [];
this.bottom = 0;
this.top = 0;
this.current = 0;
this.empty = true;
this.BOQ = true;
this.EOQ = false;
};
Here is a solution that started with this simpler answer to the similar question, Undo Redo History for Canvas FabricJs.
My answer is along the same lines as Tom's answer and the other answers that are modifications of Tom's answer.
To track the state, I'm using JSON.stringify(canvas) and canvas.loadFromJSON() like the other answers and have an event registered on the object:modified to capture the state.
One important thing is that the final canvas.renderAll() should be called in a callback passed to the second parameter of loadFromJSON(), like this
canvas.loadFromJSON(state, function() {
canvas.renderAll();
}
This is because it can take a few milliseconds to parse and load the JSON and you need to wait until that's done before you render. It's also important to disable the undo and redo buttons as soon as they're clicked and to only re-enable in the same call back. Something like this
$('#undo').prop('disabled', true);
$('#redo').prop('disabled', true);
canvas.loadFromJSON(state, function() {
canvas.renderAll();
// now turn buttons back on appropriately
...
(see full code below)
}
I have an undo and a redo stack and a global for the last unaltered state. When some modification occurs, then the previous state is pushed into the undo stack and the current state is re-captured.
When the user wants to undo, then current state is pushed to the redo stack. Then I pop off the last undo and both set it to the current state and render it on the canvas.
Likewise when the user wants to redo, the current state is pushed to the undo stack. Then I pop off the last redo and both set it to the current state and render it on the canvas.
The Code
// Fabric.js Canvas object
var canvas;
// current unsaved state
var state;
// past states
var undo = [];
// reverted states
var redo = [];
/**
* Push the current state into the undo stack and then capture the current state
*/
function save() {
// clear the redo stack
redo = [];
$('#redo').prop('disabled', true);
// initial call won't have a state
if (state) {
undo.push(state);
$('#undo').prop('disabled', false);
}
state = JSON.stringify(canvas);
}
/**
* Save the current state in the redo stack, reset to a state in the undo stack, and enable the buttons accordingly.
* Or, do the opposite (redo vs. undo)
* #param playStack which stack to get the last state from and to then render the canvas as
* #param saveStack which stack to push current state into
* #param buttonsOn jQuery selector. Enable these buttons.
* #param buttonsOff jQuery selector. Disable these buttons.
*/
function replay(playStack, saveStack, buttonsOn, buttonsOff) {
saveStack.push(state);
state = playStack.pop();
var on = $(buttonsOn);
var off = $(buttonsOff);
// turn both buttons off for the moment to prevent rapid clicking
on.prop('disabled', true);
off.prop('disabled', true);
canvas.clear();
canvas.loadFromJSON(state, function() {
canvas.renderAll();
// now turn the buttons back on if applicable
on.prop('disabled', false);
if (playStack.length) {
off.prop('disabled', false);
}
});
}
$(function() {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Set up the canvas
canvas = new fabric.Canvas('canvas');
canvas.setWidth(500);
canvas.setHeight(500);
// save initial state
save();
// register event listener for user's actions
canvas.on('object:modified', function() {
save();
});
// draw button
$('#draw').click(function() {
var imgObj = new fabric.Circle({
fill: '#' + Math.floor(Math.random() * 16777215).toString(16),
radius: Math.random() * 250,
left: Math.random() * 250,
top: Math.random() * 250
});
canvas.add(imgObj);
canvas.renderAll();
save();
});
// undo and redo buttons
$('#undo').click(function() {
replay(undo, redo, '#redo', this);
});
$('#redo').click(function() {
replay(redo, undo, '#undo', this);
})
});
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.min.js" type="text/javascript"></script>
</head>
<body>
<button id="draw">circle</button>
<button id="undo" disabled>undo</button>
<button id="redo" disabled>redo</button>
<canvas id="canvas" style="border: solid 1px black;"></canvas>
</body>
I am allowing the user to remove the last added path (in my painting application), this works fine for me:
var lastItemIndex = (fabricCanvas.getObjects().length - 1);
var item = fabricCanvas.item(lastItemIndex);
if(item.get('type') === 'path') {
fabricCanvas.remove(item);
fabricCanvas.renderAll();
}
But you could also remove the IF statement and let people remove anything.
I know its late to answer this but this is my version of implementing this. Can be useful to someone.
I have implemented this feature by saving Canvas States as JSON. Whenever a user adds or modifies an object in the Canvas, it will save the changed canvas state and maintain it in an array. This array is then manipulated whenever user clicks on Undo or Redo button.
Take a look at this link. I have also provided a working Demo URL.
https://github.com/abhi06991/Undo-Redo-Fabricjs
HTML:
<canvas id="canvas" width="400" height="400"></canvas>
<button type="button" id="undo" >Undo</button>
<button type="button" id="redo" disabled>Redo</button>
JS:
var canvasDemo = (function(){
var _canvasObject = new fabric.Canvas('canvas',{backgroundColor : "#f5deb3"});
var _config = {
canvasState : [],
currentStateIndex : -1,
undoStatus : false,
redoStatus : false,
undoFinishedStatus : 1,
redoFinishedStatus : 1,
undoButton : document.getElementById('undo'),
redoButton : document.getElementById('redo'),
};
_canvasObject.on(
'object:modified', function(){
updateCanvasState();
}
);
_canvasObject.on(
'object:added', function(){
updateCanvasState();
}
);
var addObject = function(){
var rect = new fabric.Rect({
left : 100,
top : 100,
fill : 'red',
width : 200,
height : 200
});
_canvasObject.add(rect);
_canvasObject.setActiveObject(rect);
_canvasObject.renderAll();
}
var updateCanvasState = function() {
if((_config.undoStatus == false && _config.redoStatus == false)){
var jsonData = _canvasObject.toJSON();
var canvasAsJson = JSON.stringify(jsonData);
if(_config.currentStateIndex < _config.canvasState.length-1){
var indexToBeInserted = _config.currentStateIndex+1;
_config.canvasState[indexToBeInserted] = canvasAsJson;
var numberOfElementsToRetain = indexToBeInserted+1;
_config.canvasState = _config.canvasState.splice(0,numberOfElementsToRetain);
}else{
_config.canvasState.push(canvasAsJson);
}
_config.currentStateIndex = _config.canvasState.length-1;
if((_config.currentStateIndex == _config.canvasState.length-1) && _config.currentStateIndex != -1){
_config.redoButton.disabled= "disabled";
}
}
}
var undo = function() {
if(_config.undoFinishedStatus){
if(_config.currentStateIndex == -1){
_config.undoStatus = false;
}
else{
if (_config.canvasState.length >= 1) {
_config.undoFinishedStatus = 0;
if(_config.currentStateIndex != 0){
_config.undoStatus = true;
_canvasObject.loadFromJSON(_config.canvasState[_config.currentStateIndex-1],function(){
var jsonData = JSON.parse(_config.canvasState[_config.currentStateIndex-1]);
_canvasObject.renderAll();
_config.undoStatus = false;
_config.currentStateIndex -= 1;
_config.undoButton.removeAttribute("disabled");
if(_config.currentStateIndex !== _config.canvasState.length-1){
_config.redoButton.removeAttribute('disabled');
}
_config.undoFinishedStatus = 1;
});
}
else if(_config.currentStateIndex == 0){
_canvasObject.clear();
_config.undoFinishedStatus = 1;
_config.undoButton.disabled= "disabled";
_config.redoButton.removeAttribute('disabled');
_config.currentStateIndex -= 1;
}
}
}
}
}
var redo = function() {
if(_config.redoFinishedStatus){
if((_config.currentStateIndex == _config.canvasState.length-1) && _config.currentStateIndex != -1){
_config.redoButton.disabled= "disabled";
}else{
if (_config.canvasState.length > _config.currentStateIndex && _config.canvasState.length != 0){
_config.redoFinishedStatus = 0;
_config.redoStatus = true;
_canvasObject.loadFromJSON(_config.canvasState[_config.currentStateIndex+1],function(){
var jsonData = JSON.parse(_config.canvasState[_config.currentStateIndex+1]);
_canvasObject.renderAll();
_config.redoStatus = false;
_config.currentStateIndex += 1;
if(_config.currentStateIndex != -1){
_config.undoButton.removeAttribute('disabled');
}
_config.redoFinishedStatus = 1;
if((_config.currentStateIndex == _config.canvasState.length-1) && _config.currentStateIndex != -1){
_config.redoButton.disabled= "disabled";
}
});
}
}
}
}
return {
addObject : addObject,
undoButton : _config.undoButton,
redoButton : _config.redoButton,
undo : undo,
redo : redo,
}
})();
canvasDemo.undoButton.addEventListener('click',function(){
canvasDemo.undo();
});
canvasDemo.redoButton.addEventListener('click',function(){
canvasDemo.redo();
});
canvasDemo.addObject();
My use case was drawing simple shapes akin to blueprints, so I didn't have to worry about the overhead of saving the whole canvas state. If you are in the same situation, this is very easy to accomplish. This code assumes you have a 'wrapper' div around the canvas, and that you want the undo/redo functionality bound to the standard windows keystrokes of 'CTRL+Z' and 'CTRL+Y'.
The purpose of the 'pause_saving' variable was to account for the fact that when a canvas is re-rendered it seemingly created each object one by one all over again, and we don't want to catch these events, as they aren't REALLY new events.
//variables for undo/redo
let pause_saving = false;
let undo_stack = []
let redo_stack = []
canvas.on('object:added', function(event){
if (!pause_saving) {
undo_stack.push(JSON.stringify(canvas));
redo_stack = [];
console.log('Object added, state saved', undo_stack);
}
});
canvas.on('object:modified', function(event){
if (!pause_saving) {
undo_stack.push(JSON.stringify(canvas));
redo_stack = [];
console.log('Object modified, state saved', undo_stack);
}
});
canvas.on('object:removed', function(event){
if (!pause_saving) {
undo_stack.push(JSON.stringify(canvas));
redo_stack = [];
console.log('Object removed, state saved', undo_stack);
}
});
//Listen for undo/redo
wrapper.addEventListener('keydown', function(event){
//Undo - CTRL+Z
if (event.ctrlKey && event.keyCode == 90) {
pause_saving=true;
redo_stack.push(undo_stack.pop());
let previous_state = undo_stack[undo_stack.length-1];
if (previous_state == null) {
previous_state = '{}';
}
canvas.loadFromJSON(previous_state,function(){
canvas.renderAll();
})
pause_saving=false;
}
//Redo - CTRL+Y
else if (event.ctrlKey && event.keyCode == 89) {
pause_saving=true;
state = redo_stack.pop();
if (state != null) {
undo_stack.push(state);
canvas.loadFromJSON(state,function(){
canvas.renderAll();
})
pause_saving=false;
}
}
});
You can use "object:added" and/or "object:removed" for that — fabricjs.com/events
You can follow this post:
Do we have canvas Modified Event in Fabric.js?
I know the answer is already chosen but here is my version, script is condensed, also added a reset to original state. After any event you want to save just call saveState(); jsFiddle
canvas = new fabric.Canvas('canvas', {
selection: false
});
function saveState(currentAction) {
currentAction = currentAction || '';
// if (currentAction !== '' && lastAction !== currentAction) {
$(".redo").val($(".undo").val());
$(".undo").val(JSON.stringify(canvas));
console.log("Saving After " + currentAction);
lastAction = currentAction;
// }
var objects = canvas.getObjects();
for (i in objects) {
if (objects.hasOwnProperty(i)) {
objects[i].setCoords();
}
}
}
canvas.on('object:modified', function (e) {
saveState("modified");
});
// Undo Canvas Change
function undo() {
canvas.loadFromJSON($(".redo").val(), canvas.renderAll.bind(canvas));
}
// Redo Canvas Change
function redo() {
canvas.loadFromJSON($(".undo").val(), canvas.renderAll.bind(canvas));
};
$("#reset").click(function () {
canvas.loadFromJSON($("#original_canvas").val(),canvas.renderAll.bind(canvas));
});
var bgnd = new fabric.Image.fromURL('https://s3-eu-west-1.amazonaws.com/kienzle.dev.cors/img/image2.png', function(oImg){
oImg.hasBorders = false;
oImg.hasControls = false;
// ... Modify other attributes
canvas.insertAt(oImg,0);
canvas.setActiveObject(oImg);
myImg = canvas.getActiveObject();
saveState("render");
$("#original_canvas").val(JSON.stringify(canvas.toJSON()));
});
$("#undoButton").click(function () {
undo();
});
$("#redoButton").click(function () {
redo();
});
i developed a small script for you,hope it will help you .see this demo Fiddle
although redo is not perfect you have to click minimum two time at undo button then redo work .you can easily solve this problem with giving simple conditions in redo code.
//Html
<canvas id="c" width="400" height="200" style=" border-radius:25px 25px 25px 25px"></canvas>
<br>
<br>
<input type="button" id="addtext" value="Add Text"/>
<input type="button" id="undo" value="Undo"/>
<input type="button" id="redo" value="redo"/>
<input type="button" id="clear" value="Clear Canvas"/>
//script
var canvas = new fabric.Canvas('c');
var text = new fabric.Text('Sample', {
fontFamily: 'Hoefler Text',
left: 50,
top: 30,
//textAlign: 'center',
fill: 'navy',
});
canvas.add(text);
var vall=10;
var l=0;
var flag=0;
var k=1;
var yourJSONString = new Array();
canvas.observe('object:selected', function(e) {
//yourJSONString = JSON.stringify(canvas);
if(k!=10)
{
yourJSONString[k] = JSON.stringify(canvas);
k++;
}
j = k;
var activeObject = canvas.getActiveObject();
});
$("#undo").click(function(){
if(k-1!=0)
{
canvas.clear();
canvas.loadFromJSON(yourJSONString[k-1]);
k--;
l++;
}
canvas.renderAll();
});
$("#redo").click(function(){
if(l > 1)
{
canvas.clear();
canvas.loadFromJSON(yourJSONString[k+1]);
k++;
l--;
canvas.renderAll();
}
});
$("#clear").click(function(){
canvas.clear();
});
$("#addtext").click(function(){
var text = new fabric.Text('Sample', {
fontFamily: 'Hoefler Text',
left: 100,
top: 100,
//textAlign: 'center',
fill: 'navy',
});
canvas.add(text);
});
I have answer to all your queries :) get a smile
check this link.. its all done ... copy & paste it :P
http://jsfiddle.net/SpgGV/27/
var canvas = new fabric.Canvas('c');
var current;
var list = [];
var state = [];
var index = 0;
var index2 = 0;
var action = false;
var refresh = true;
state[0] = JSON.stringify(canvas.toDatalessJSON());
console.log(JSON.stringify(canvas.toDatalessJSON()));
$("#clear").click(function(){
canvas.clear();
index=0;
});
$("#addtext").click(function(){
++index;
action=true;
var text = new fabric.Text('Sample', {
fontFamily: 'Hoefler Text',
left: 100,
top: 100,
//textAlign: 'center',
fill: 'navy',
});
canvas.add(text);
});
canvas.on("object:added", function (e) {
if(action===true){
var object = e.target;
console.log(JSON.stringify(canvas.toDatalessJSON()));
state[index] = JSON.stringify(canvas.toDatalessJSON());
refresh = true;
action=false;
canvas.renderAll();
}
});
function undo() {
if (index < 0) {
index = 0;
canvas.loadFromJSON(state[index]);
canvas.renderAll();
return;
}
console.log('undo');
canvas.loadFromJSON(state[index]);
console.log(JSON.stringify(canvas.toDatalessJSON()));
canvas.renderAll();
action = false;
}
function redo() {
action = false;
if (index >= state.length - 1) {
canvas.loadFromJSON(state[index]);
canvas.renderAll();
return;
}
console.log('redo');
canvas.loadFromJSON(state[index]);
console.log(JSON.stringify(canvas.toDatalessJSON()));
canvas.renderAll();
canvas.renderAll();
}
canvas.on("object:modified", function (e) {
var object = e.target;
console.log('object:modified');
console.log(JSON.stringify(canvas.toDatalessJSON()));
state[++index] = JSON.stringify(canvas.toDatalessJSON());
action=false;
});
$('#undo').click(function () {
index--;
undo();
});
$('#redo').click(function () {
index++;
redo();
});
So I'm making a simple steganography tool (encrypting messages within images) and exposing it as a web service via Node.js. I am very new to Javascript and Node.js in particular. The app first converts a text string into a binary string by changing each character into an 8-bit ASCII encoding, resulting in one large binary string. I then encrypt the message within the pixels. Even values of pixels represent 0s from the binary, and odd values represent 1s. The end of the string is marked as 3 pixels of value 100 in a row (this is temporary, until I figure out a better way to mark the end). I'm using a node.js library called 'pngjs' that gives me pixel-level access to png images.
So I have a problem with the decodeMessage function. It builds up the string message, and is then meant to return it, however the return call at the end results in undefined.
How can I fix it?
Thanks in advance for the help!
function encodeMessage(image, mes) {
var message = mes;
var fs = require('fs'),
PNG = require('pngjs').PNG;
fs.createReadStream(image)
.pipe(new PNG({
filterType: 4
}))
.on('parsed', function() {
for (var y = 0; y < this.height; y++) {
for (var x = 0; x < this.width; x++) {
var idx = (this.width * y + x);// << 2;
//console.log(idx);
if (idx < message.length) {
var item = message.charAt(idx);
/* if the character in the encoded string is 0 */
if (item == 0) {
/* if the pixel is odd, we want it to be even */
if (this.data[idx] % 2 == 1) {
/* if the pixel is 0, add 1 to it */
if (this.data[idx] == 0) {
this.data[idx] = this.data[idx] + 1;
} else {
/* else substract 1 */
this.data[idx] = this.data[idx] - 1;
}
}
} else {
/* if the character in the encoded string is 1 */
if (this.data[idx] % 2 == 0) {
if (this.data[idx] == 0) {
this.data[idx] = this.data[idx] + 1;
} else {
this.data[idx] = this.data[idx] - 1;
}
}
}
//console.log(this.data[idx]);
} else if (idx === message.length) {
/* do something to the first pixel following the end of the string */
this.data[idx] = 100;
this.data[idx+1] = 100;
this.data[idx+2] = 100;
//console.log(this.data[idx]);
} else {
/* do something to the remaining pixels */
}
}
}
this.pack().pipe(fs.createWriteStream('encoded_' + image));
});
}
function decodeMessage(image) {
var message = "";
var fs = require('fs'),
PNG = require('pngjs').PNG;
fs.createReadStream(image)
.pipe(new PNG({
filterType: 4
}))
.on('parsed', function() {
dance:
for (var y = 0; y < this.height; y++) {
for (var x = 0; x < this.width; x++) {
var idx = (this.width * y + x);// << 2;
if (this.data[idx] == 100 && this.data[idx+1] == 100 && this.data[idx+2] == 100) {
break dance;
} else {
if (this.data[idx] % 2 == 0) {
message += "0";
} else {
message += "1";
}
}
}
}
/* the message outputs correctly over here */
console.log(message);
//return message;
});
/* but the return of the variable here doesn't work */
return message;
}
exports.encodeMessage = encodeMessage;
exports.decodeMessage = decodeMessage;
The parsed event is fired asynchronously, so you cannot return a value from decodeMessage.
function decodeMessage(image, cb) {
// Code
.on('parsed', function() {
// Code
console.log(message);
cb(message);
});
}
Then you must pass a callback to your decodeMessage function.
decodeMessage(image, function(decoded){
// Here is the decoded data.
});
The same is true for your encodeMessage function. The function will return before encoding has finished. If you want to know when it is done, you need to pass a callback the same way.