Canvas 7, Saving and Loading
The last version of my Canvas app let you pick different brushes and draw on the grid. If you played around with the line brush you might have noticed a small bug. The line erases everything it passes over. This is because the line has no idea what the image was supposed to look like before we started drawing.
Welcome to saving. If we save what the image looks like before we start drawing the line, then we won’t get this bug. (Plus we’ll have to save at some point anyway.)
So how do we save? We could get the color of ever cell and save it into an array. Or we could save the entire canvas as an actual image. I’m not 100% on which method is faster yet, but I’m betting it’s the image method. (I base this assumption on poking around the Canvas forums on the internet.)
To save the canvas, we are going to use the .toDataURL() method. This returns a data url string of the current canvas image.
To load the image, we need to simply draw what we saved right? Turn the data url into an image object by setting it as the source, and then use .drawImage() to draw it. Except this doesn’t work. If you set an img tag on the page to have a src of the data url, then it does work! I’m not 100% of why this is the case, but I can solve it. You need to draw the image on the canvas in the image’s onload event. Then you set the source and everything works as expected.
Let me show you.
//
// Get the data URL of the Canvas
//
function getImage()
{
return $("#canvas").toDataURL();
}
//
// Load the Data URL image
//
function setImage(dataURL)
{
var img = new Image();
img.onload = function(){
//Have to draw on the Canvas here for the image to load properly
$("#canvas")[0].getContext("2d").drawImage(img, 0, 0);
}
img.src = dataURL;
}
So my first thought to solve the line problem was to save the image before we start drawing the line. Then each update restore the original image and draw our new line on top of it. The problem with this is that img.onload issue. I have to draw the line inside of that method or else it won’t show up. So I modified the setImage method to take a function as a paramter. Now I could update my Brush’s onMouseMove like so
//
// Load the Data URL image
//
setImage: function(dataURL, drawOperations)
{
var img = new Image();
img.onload = function(){
//Have to draw on the Canvas here for the image to load properly
Grid.display.drawImage(img, 0, 0);
//Do any other draw operations in here
if( undefined != drawOperations ){
drawOperations();
}
}
img.src = dataURL;
},
//
// Draw the line
//
onMouseMove: function(evt)
{
if( LineBrush.shouldDraw )
{
Grid.setImage( LineBrush.orginalData, function(){
//Get the Cell from the grid
LineBrush.overCell = Grid.getCellFromEvent(evt);
//Draw the new line
LineBrush.drawLine( LineBrush.color1 );
});
}
},
Here is the final result.
Canvas Test 7
And the code
//
// Orginal From http://www.javascripter.net/faq/rgbtohex.htm
// Added PDAtoHex()
//
function PDAtoHex(pixalDataArray)
{
return "#" + toHex(pixalDataArray[0]) + toHex(pixalDataArray[1]) + toHex(pixalDataArray[2]);
}
function toHex(N) {
if (N==null) return "00";
N=parseInt(N); if (N==0 || isNaN(N)) return "00";
N=Math.max(0,N); N=Math.min(N,255); N=Math.round(N);
return "0123456789ABCDEF".charAt((N-N%16)/16)
+ "0123456789ABCDEF".charAt(N%16);
}
//
// Controls the Grid
var Grid = {
canvasID: "display", //ID for the canvas element
display: null, //The Drawing context
jQueryCanvas: null, //jQuery object for the canvas
//Canvas Size
canvasWidth: 500,
canvasHeight: 400,
//Columns and Rows
numberOfColumns: 25,
numberOfRows: 25,
//Colors for the Grid
backgroundColor: "#FFF",
gridColor: "#ccf",
gridThickness: "2",
//Size of the cells
cellWidth: function() { return this.canvasWidth/this.numberOfColumns; },
cellHeight: function() { return this.canvasHeight/this.numberOfRows; },
//
//Draw the Grid
// Uses the gridColor and gridThickness
drawGrid : function(){
//Set the colors
this.display.strokeStyle = this.gridColor;
this.display.lineWidth = this.gridThickness;
//Start Draw
this.display.beginPath();
//Draw the Vertical lines
for(var i=0; i <= this.numberOfColumns; i++){
//Move to the top of the canvas
this.display.moveTo(i*this.cellWidth(), 0)
//Draw the line
this.display.lineTo( i*this.cellWidth(), this.canvasHeight );
}
//Draw the Horizontal lines.
for(var i=0; i <= this.numberOfRows; i++){
//Move to the left of the canvas
this.display.moveTo(0, i*this.cellHeight())
//Draw the line
this.display.lineTo( this.canvasWidth, i*this.cellHeight() );
}
//Stop Draw
this.display.stroke();
},
//
//Draw the Background
// Uses backgroundColor
drawBackground : function(){
this.display.fillStyle = this.backgroundColor;
this.display.fillRect(0,0, this.canvasWidth, this.canvasHeight);
},
//
//Draw/color a cell
// Takes: Cell object with column,row
drawCell : function(cell, color){
//Figure out the position
var y_pos = cell.row * this.cellHeight();
var x_pos = cell.column * this.cellWidth();
//Set the Color
this.display.fillStyle = color;
//Draw the cell
this.display.fillRect( x_pos, y_pos, this.cellWidth(), this.cellHeight());
//Draw the cell outline
this.display.strokeRect( x_pos, y_pos, this.cellWidth(), this.cellHeight());
},
//
//Get the Cell The Mouse is in
// Returns: Cell object with column,row
getCellFromEvent : function(evt){
var offset = this.jQueryCanvas.offset();
//Create an Object to hold the column/row
var cell = {
column: -1,
row: -1,
};
//Find the poistion of the mouse
var x_pos = Math.floor(evt.pageX - offset.left);
var y_pos = Math.floor(evt.pageY - offset.top);
//Now which column was it clicked?
for(var column=0; column < this.numberOfColumns; column++){
//Find the column
if( x_pos >= column*this.cellWidth() && x_pos <= (column+1)*this.cellWidth() ){
//Found it!
cell.column = column;
//Fow find the row
for(var row=0; row < this.numberOfRows; row++){
//Find the row
if( y_pos >= row*this.cellHeight() && y_pos <= (row+1)*this.cellHeight()){
//Found it!
cell.row = row;
}
} //End Row
}
} // End Column
//Return the found position
return cell;
},
//
//Gets the color of a cell
// Returns: CanvasPixelArray
getColorFromCell: function(cell){
//Figure out the position
var y_pos = cell.row * this.cellHeight();
var x_pos = cell.column * this.cellWidth();
return this.display.getImageData( x_pos+1, y_pos+1, 1, 1).data;
},
//
//Initalize the Grid
//
init: function(){
this.jQueryCanvas = $("#"+this.canvasID);
this.jQueryCanvas.attr({
width: this.canvasWidth,
height: this.canvasHeight
});
//Set the Drawing context
this.display = this.jQueryCanvas[0].getContext("2d");
//Draw the Background
this.drawBackground();
//Draw the Grid
this.drawGrid();
},
//
// Set the Brush to Use
//
setBrush: function(brushObject)
{
this.jQueryCanvas
//UnBind all the Old brush stuff
.unbind("mousedown")
.unbind("mousemove")
.unbind("mouseup")
//Bind our Grid to the Brushes Methods
.bind("mousedown", brushObject.onMouseDown)
.bind("mousemove", brushObject.onMouseMove)
.bind("mouseup", brushObject.onMouseUp)
},
//
// Get the data URL of the Canvas
//
getImage: function()
{
return this.jQueryCanvas[0].toDataURL();
},
//
// Load the Data URL image
//
setImage: function(dataURL, drawOperations)
{
var img = new Image();
img.onload = function(){
//Have to draw on the Canvas here for the image to load properly
Grid.display.drawImage(img, 0, 0);
//Do any other draw operations in here
if( undefined != drawOperations ){
drawOperations();
}
}
img.src = dataURL;
},
};
//
// Simple Brush
// Colors Primary on Click, Secondary if primary is already the color.
var SimpleDrawBrush = {
//Colors used for the brush
color1: "#00FF00",
color2: "#FFFFFF",
onMouseDown: function(evt){
//We Used jQuery to bind it, so we are getting a jQuery object as this
//Get the Cell from the grid
var cell = Grid.getCellFromEvent(evt);
//Get the current color of the cell.
var color = Grid.getColorFromCell(cell);
//Is it already the Primary color?
if( PDAtoHex(color) == SimpleDrawBrush.color1 ){
//Yes, use the secondary color
Grid.drawCell(cell, SimpleDrawBrush.color2);
} else {
//Use the Primary Color
Grid.drawCell(cell, SimpleDrawBrush.color1);
}
},
onMouseMove: function(evt)
{
//Do nothing, this method just has to be defined.
},
onMouseUp: function(evt)
{
//Do nothing, this method just has to be defined.
},
};
//
// Hold Brush
// Keeps coloring for as long as the mouse in down.
var HoldDrawBrush = {
//Color for the brush
color1: "#FF0000",
shouldDraw: false,
//
// Start the coloring process
onMouseDown: function(evt)
{
//Get the Cell from the grid
var cell = Grid.getCellFromEvent(evt);
//Use the Primary Color
Grid.drawCell(cell, HoldDrawBrush.color1);
//Tell it we should be drawing
HoldDrawBrush.shouldDraw = true;
},
//
// Continue Coloring
onMouseMove: function(evt)
{
if( HoldDrawBrush.shouldDraw )
{
//Get the Cell from the grid
var cell = Grid.getCellFromEvent(evt);
//Use the Primary Color
Grid.drawCell(cell, HoldDrawBrush.color1);
}
},
onMouseUp: function(evt)
{
//Stop drawing
HoldDrawBrush.shouldDraw = false;
},
};
//
// Line Brush
//
var LineBrush = {
color1: "#0000FF",
color2: "#FFFFFF",
shouldDraw: false,
startCell: {column: -1, row: -1},
overCell: {column: -1, row: -1},
orginalData: null,
onMouseDown: function(evt)
{
//First, save the image as it currently stands
LineBrush.orginalData = Grid.getImage();
//Get the Cell from the grid
LineBrush.startCell = Grid.getCellFromEvent(evt);
//Use the Primary Color
Grid.drawCell(LineBrush.startCell, LineBrush.color);
//Tell it we should be drawing
LineBrush.shouldDraw = true;
},
onMouseMove: function(evt)
{
if( LineBrush.shouldDraw )
{
Grid.setImage( LineBrush.orginalData, function(){
//Get the Cell from the grid
LineBrush.overCell = Grid.getCellFromEvent(evt);
//Draw the new line
LineBrush.drawLine( LineBrush.color1 );
});
}
},
onMouseUp: function(evt)
{
//Stop drawing
LineBrush.shouldDraw = false;
},
//
// Draws a line from the start cell to the over cell.
//
drawLine: function(color)
{
//Save as varibles for ease of reading
var a_column = LineBrush.startCell.column;
var a_row = LineBrush.startCell.row;
var b_column = LineBrush.overCell.column;
var b_row = LineBrush.overCell.row;
//Get the length of the line
var line_length = Math.sqrt( Math.pow( a_column - b_column, 2) + Math.pow( a_row - b_row, 2));
//Now draw each point
for(var i=0; i < line_length; i++){
//Get the Row/Column of the cell to color
var cell = {};
cell.column = Math.round( b_column + ( a_column - b_column)*i/line_length);
cell.row = Math.round( b_row + ( a_row - b_row)*i/line_length);
//Now redraw it
Grid.drawCell( cell, color);
}
},
};
You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
