Skip to content

Instantly share code, notes, and snippets.

@jennschiffer
Created January 24, 2014 15:43
Show Gist options
  • Save jennschiffer/8599766 to your computer and use it in GitHub Desktop.
Save jennschiffer/8599766 to your computer and use it in GitHub Desktop.
christopher clay's canvaspaint r1
// CanvasPaint r1
// by Christopher Clay <[email protected]>
//
// The code in this file is dedicated to the Public Domain
// http://creativecommons.org/licenses/publicdomain/
//
//some global vars
var canvas, c, canvastemp, ctemp, canvassel, csel, canvasundo, cundo, wsp, imgd, co, check;
var iface = { dragging:false, resizing:false, status:null, xy:null, txy:null }
var prefs = { pretty:false, controlpoints:false }
window.onload = function() {
wsp = document.getElementById('workspace');
//set up canvas
canvas = document.getElementById('canvas');
if(canvas.getContext) {
c = canvas.getContext("2d");
iface.status = document.getElementById('status').firstChild;
iface.xy = document.getElementById('xy').firstChild;
iface.txy = document.getElementById('txy').firstChild;
//set up defaults
c.tool = new tool.pencil();
c.lineWidth = 1;
c.strokeStyle = '#000';
c.fillStyle = '#FFF';
c.tertStyle = '#DDD';
c.strokeFill = 1; //outline shapes
prefs.pretty = document.getElementById('pretty').checked;
prefs.controlpoints = document.getElementById('controlpoints').checked;
c.fillRect(0, 0, canvas.width, canvas.height); //fill with background color (=not transparent)
//alert(getPixel(0, 0));
//set up overlay canvas (for preview when drawing lines, rects etc)
canvastemp = document.getElementById("canvastemp");
ctemp = canvastemp.getContext("2d");
//set up selection canvas (invisible, used for selections)
canvassel = document.getElementById("canvassel");
csel = canvassel.getContext("2d");
//set up undo canvas (invisible)
canvasundo = document.getElementById("canvasundo");
cundo = canvasundo.getContext("2d");
//draw title bar gradient
gradientcanvas = document.getElementById('gradient');
if(gradientcanvas.getContext){
var g = gradientcanvas.getContext('2d');
grad = g.createLinearGradient(0, 0, 200, 15);
grad.addColorStop(0, '#036');
grad.addColorStop(1, '#ACF');
g.fillStyle = grad;
g.fillRect(0, 0, 300, 17);
}
//set up events
window.onmouseup = bodyUp;
window.onmousemove = bodyMove;
window.onkeydown = shortcuts;
canvas.onmousedown = canvastemp.onmousedown = c_down;
canvas.onmousemove = canvastemp.onmousemove = c_move;
canvas.onmouseout = canvastemp.onmouseout = c_out;
canvas.onmouseup = canvastemp.onmouseup = c_up;
if(document.location.search) {
paint.open(document.location.search.substring(1));
}
} else { //go away, IE!
document.getElementById('overlaybg').style.display = 'block';
document.getElementById('overlay').style.display = 'block';
}
}
function shortcuts(e) {
if(e.keyCode == 46) { //delete
if(c.tool.name == 'select' && c.tool.status > 0) { //del selection
c.tool.del();
}
} else if(e.ctrlKey || e.metaKey) {
var letter = String.fromCharCode(e.keyCode);
switch(letter) {
case 'A':
sel_all();
break;
case 'C':
copy();
break;
case 'V':
paste();
break;
case 'X':
cut();
break;
case 'Z':
undo();
break;
}
}
return false;
}
function sel_all() {
selTool(document.getElementById('select'));
c.tool.all();
e.preventDefault();
e.stopPropagation();
}
function sel_cancel() {
if(c.tool.status == 2) { c.drawImage(canvassel, Math.floor(ctemp.start.x), Math.floor(ctemp.start.y)); }
if(c.tool.status != 4) { canvastemp.style.display='none'; }
}
function copy() {
if(c.tool.name == 'select' && c.tool.status > 0) { //copy selection
c.tool.copy();
}
}
function cut() {
if(c.tool.name == 'select' && c.tool.status > 0) { //cut selection
c.tool.copy();
c.tool.del();
}
}
function paste() {
//todo only if something on canvassel
selTool(document.getElementById('select'));
c.tool.paste();
}
function undo() {
if(c.tool.name == 'select') { //reset all info about current selection
activateTempCanvas();
canvastemp.style.display='none';
c.tool.status = 0;
}
undoLoad();
}
function getxy(e, o) {
//gets mouse position relative to object o
if(c) {
var bo = getpos(o);
var x = e.clientX - bo.x + wsp.scrollLeft; //correct for canvas position, workspace scroll offset
var y = e.clientY - bo.y + wsp.scrollTop;
x += document.documentElement.scrollLeft; //correct for window scroll offset
y += document.documentElement.scrollTop;
x = (c.zoom) ? x/c.zoom : x; //correct for zoom
y = (c.zoom) ? y/c.zoom : y;
return { x: x-.5, y: y-.5 }; //-.5 prevents antialiasing of stroke lines
}
}
function getpos(o) {
//gets position of object o
var bo, x, y, b; x = y = 0;
if(document.getBoxObjectFor) { //moz
bo = document.getBoxObjectFor(o);
x = bo.x; y = bo.y;
} else if (o.getBoundingClientRect) { //ie (??)
bo = o.getBoundingClientRect();
x = bo.left; y = bo.top;
} else { //opera, safari etc
while(o && o.nodeName != 'BODY') {
x += o.offsetLeft;
y += o.offsetTop;
b = parseInt(document.defaultView.getComputedStyle(o,null).getPropertyValue('border-width'));
if(b > 0) { x += b; y +=b; }
o = o.offsetParent;
}
}
return { x:x, y:y }
}
function zoomTo(level) {
//changes zoom level (by css-sizing canvas)
var lastzoom = c.zoom;
if(lastzoom) { //to diff zoom level: scale back first
canvas.style.width=canvastemp.style.width=canvas.offsetWidth/c.zoom+'px';
canvas.style.height=canvastemp.style.height=canvas.offsetHeight/c.zoom+'px';
c.zoom=null;
//todo: fix scrollbars
}
if(lastzoom != level && level > 1) {
c.zoom=level;
canvas.style.width=canvastemp.style.width=level*canvas.offsetWidth+'px';
canvas.style.height=canvastemp.style.height=level*canvas.offsetHeight+'px';
}
//todo: move resize indicator
}
var tool = {
_shapes: function() {
this.down = this._down = function() {
undoSave();
activateTempCanvas();
this.start = { x:m.x, y:m.y }
this.status = 1;
c.beginPath();
}
this._move = function() {
ctemp.clearRect(0, 0, canvastemp.width, canvastemp.height);
iface.txy.innerHTML = Math.round(m.x-this.start.x)+'x'+Math.round(m.y-this.start.y);
}
this._up = function() {
canvastemp.style.display='none';
this.status = 0;
iface.txy.innerHTML = '&nbsp;';
}
},
_brushes: function() {
this.down = function() {
this.last = null;
this.cp = null;
this.lastcp = null;
this.disconnected = null;
c.beginPath();
undoSave();
this.sstart = this.last = { x:m.x, y:m.y } //extra s in sstart to not affect status bar display
this.status = 1;
}
this.move = function(e) {
if(this.disconnected) { //re-entering canvas: dont draw a line
iface.status.innerHTML = 'reentering';
this.disconnected = null;
this.last = { x:m.x, y:m.y }
} else { //draw connecting line
this.draw();
}
c.moveTo(m.x, m.y);
}
this.up = function() {
if(this.sstart && this.sstart.x == m.x && this.sstart.y == m.y) {
drawDot(m.x, m.y, c.lineWidth, c.strokeStyle);
}
this.sstart = null;
this.status = 0;
}
this.draw = function() {
if(prefs.pretty) {
//calculate control point
this.cp = { x:m.x, y:m.y } //default: no bezier
var deltax = Math.abs(m.x-this.last.x);
var deltay = Math.abs(m.y-this.last.y);
if(this.last && (deltax+deltay > 10)) { //long line
//had no control point last time: use last vertex
var lx = (this.lastcp) ? this.lastcp.x : this.last.x; //should be last2x?
var ly = (this.lastcp) ? this.lastcp.y : this.last.y;
var delta2x = this.last.x-lx; var delta2y = this.last.y-ly;
this.cp = { x:lx+delta2x*1.4, y:ly+delta2y*1.4 }
}
this.lastcp = { x:this.cp.x, y:this.cp.y }
c.bezierCurveTo(this.cp.x, this.cp.y, m.x, m.y, m.x, m.y); //make pretty curve, first two params =control pt
c.stroke();
c.beginPath();
if(prefs.controlpoints) {
if(!(this.cp.x==m.x && this.cp.y==m.y)) { drawDot(this.cp.x, this.cp.y, 3, 'blue'); }
drawDot(this.last.x, this.last.y, 3, 'red');
}
} else { //unpretty
c.lineTo(m.x, m.y);
c.stroke();
c.beginPath();
if(prefs.controlpoints) {
drawDot(m.x, m.y, 3, 'red');
}
}
this.last = { x:m.x, y:m.y }
}
},
pencil: function() {
this.name = 'pencil';
this.status = 0;
this.inherit = tool._brushes; this.inherit();
c.lineCap = 'butt';
c.lineWidth = 1;
},
brush: function() {
this.name = 'brush';
this.status = 0;
this.inherit = tool._brushes; this.inherit();
},
eraser: function() {
this.name = 'eraser';
this.status = 0;
this.inherit = tool._brushes; this.inherit();
c.lineCap = 'square';
c.lineWidth = 7;
c.lastStrokeStyle = c.strokeStyle;
c.strokeStyle = c.fillStyle; //'#FFF'; //selCol('#FFF');
},
line: function() {
this.name = 'line';
this.status = 0;
this.inherit = tool._shapes; this.inherit();
c.lineCap = 'round';
c.lineWidth = 1;
this.move = function(e) {
this._move();
drawLine(this.start.x, this.start.y, m.x, m.y, e.shiftKey, ctemp);
}
this.up = function(e) {
this._up();
drawLine(this.start.x, this.start.y, m.x, m.y, e.shiftKey, c);
}
},
rectangle: function() {
this.name = 'rectangle';
this.status = 0;
this.inherit = tool._shapes; this.inherit();
this.move = function(e) {
this._move();
drawRectangle(this.start.x, this.start.y, m.x, m.y, e.shiftKey, ctemp);
}
this.up = function(e) {
this._up();
drawRectangle(this.start.x, this.start.y, m.x, m.y, e.shiftKey, c);
}
},
ellipse: function() {
this.name = 'ellipse';
this.status = 0;
this.inherit = tool._shapes; this.inherit();
this.down = function(e) {
this._down();
this.lastLineWidth = c.lineWidth;
if(c.strokeFill == 3) { c.lineWidth+=1.1; ctemp.lineWidth+=1.1; } //hm
}
this.move = function(e) {
this._move();
drawEllipse(this.start.x, this.start.y, m.x, m.y, e.shiftKey, ctemp);
}
this.up = function(e) {
this._up();
drawEllipse(this.start.x, this.start.y, m.x, m.y, e.shiftKey, c);
if(c.strokeFill == 3) { c.lineWidth = this.lastLineWidth; ctemp.lineWidth = this.lastLineWidth; }
}
},
rounded: function() {
this.name = 'rounded';
this.status = 0;
this.inherit = tool._shapes; this.inherit();
this.move = function(e) {
this._move();
drawRounded(this.start.x, this.start.y, m.x, m.y, e.shiftKey, ctemp);
}
this.up = function(e) {
this._up();
drawRounded(this.start.x, this.start.y, m.x, m.y, e.shiftKey, c);
}
},
curve: function() {
this.name = 'curve';
this.status = 0;
c.lineCap = 'round';
c.lineWidth = 1;
this.down = function() {
if(this.status==0) { //starting
undoSave();
activateTempCanvas();
this.start = { x:m.x, y:m.y }
this.end = null;
this.bezier1 = null;
this.status = 5;
c.beginPath();
} else if(this.status==4 || this.status==2) { //continuing
this.status--;
}
}
this.move = function(e) {
if(this.status==5) {
ctemp.clearRect(0, 0, canvastemp.width, canvastemp.height);
drawLine(this.start.x, this.start.y, m.x, m.y, e.shiftKey, ctemp);
ctemp.stroke();
iface.txy.innerHTML = Math.round(m.x-this.start.x)+'x'+Math.round(m.y-this.start.y);
} else if(this.status == 3 || this.status == 1) {
ctemp.clearRect(0, 0, canvastemp.width, canvastemp.height);
ctemp.moveTo(this.start.x, this.start.y);
var b1x = (this.bezier1) ? this.bezier1.x : m.x;
var b1y = (this.bezier1) ? this.bezier1.y : m.y;
var b2x = (this.bezier1) ? m.x : this.end.x;
var b2y = (this.bezier1) ? m.y : this.end.y;
ctemp.bezierCurveTo(b1x, b1y, b2x, b2y, this.end.x, this.end.y);
ctemp.stroke();
iface.txy.innerHTML = Math.round(this.end.x-this.start.x)+'x'+Math.round(this.end.y-this.start.y);
}
}
this.up = function() {
if(this.status==5) { //setting endpoint // && source.id != 'body'
this.end = { x:m.x, y:m.y }
this.status = 4;
} else if(this.status==3) { //setting bezier1 && source.id != 'body'
this.bezier1 = { x:m.x, y:m.y }
ctemp.clearRect(0, 0, canvastemp.width, canvastemp.height);
ctemp.moveTo(this.start.x, this.start.y);
ctemp.bezierCurveTo(m.x, m.y, this.end.x, this.end.y, this.end.x, this.end.y);
ctemp.stroke();
this.status = 2;
} else if(this.status==1) { //setting bezier2 && source.id != 'body'
canvastemp.style.display='none';
c.moveTo(this.start.x, this.start.y);
c.bezierCurveTo(this.bezier1.x, this.bezier1.y, m.x, m.y, this.end.x, this.end.y);
c.stroke();
this.status = 0;
iface.txy.innerHTML = '&nbsp;';
}
}
},
polygon: function() {
this.name = 'polygon';
this.status = 0;
this.points = new Array();
this.down = function() {
if(this.status==0) { //starting poly
undoSave();
activateTempCanvas();
this.start = { x:m.x, y:m.y }
this.last = null;
this.status = 3;
this.points = new Array();
c.beginPath();
} else if(this.status==1) { //adding points
this.status = 2;
}
}
this.move = function(e) {
if(this.status == 3) { //first polyline
ctemp.clearRect(0, 0, canvastemp.width, canvastemp.height);
drawLine(this.start.x, this.start.y, m.x, m.y, e.shiftKey, ctemp);
} else if(this.status == 2) { // next polyline
ctemp.clearRect(0, 0, canvastemp.width, canvastemp.height);
drawLine(this.last.x, this.last.y, m.x, m.y, e.shiftKey, ctemp);
}
iface.txy.innerHTML = Math.round(m.x-this.start.x)+'x'+Math.round(m.y-this.start.y);
}
this.up = function(e) {
if(Math.abs(m.x-this.start.x) < 4 && Math.abs(m.y-this.start.y) < 4) { //closing
this.close();
} else {
ctemp.clearRect(0, 0, canvastemp.width, canvastemp.height);
var fromx = (this.status==2) ? this.last.x : this.start.x;
var fromy = (this.status==2) ? this.last.y : this.start.y;
var end = drawLine(fromx, fromy, m.x, m.y, e.shiftKey, c); //TODO cant drawline on c yet...3rd canvas??
this.last = { x:m.x, y:m.y };
this.points[this.points.length] = { x:m.x, y:m.y };
this.status = 1;
}
trg.stroke();
}
this.close = function() {
if(this.last.x) { // not just started
c.beginPath();
c.moveTo(this.start.x, this.start.y);
for(var i=0; i<this.points.length; i++) {
c.lineTo(this.points[i].x, this.points[i].y);
}
c.lineTo(this.last.x, this.last.y);
c.lineTo(this.start.x, this.start.y);
if(c.strokeFill == 2 || c.strokeFill == 3) { c.fill(); }
if(c.strokeFill == 1 || c.strokeFill == 3) { c.stroke(); }
c.fill();
} else {
//iface.txy.innerHTML = 'aborted';
}
canvastemp.style.display='none';
this.status = 0;
}
},
airbrush: function() {
this.name = 'airbrush';
this.status = 0;
c.lineCap = 'square';
this.down = function() {
undoSave();
this.drawing = setInterval('c.tool.draw()', 50);
this.last = { x:m.x, y:m.y }
this.lineCap = 'square';
this.status = 1;
}
this.move = function(e) {
this.last = { x:m.x, y:m.y }
}
this.up = function(e) {
clearInterval(this.drawing);
this.status = 0;
}
this.draw = function() {
//iface.txy.innerHTML = this.last.x+'/'+this.last.y;
c.save();
c.beginPath();
c.arc(this.last.x, this.last.y, c.lineWidth*4, 0, Math.PI*2, true);
c.clip();
for(var i=c.lineWidth*15; i>0; i--) {
var rndx = c.tool.last.x + Math.round(Math.random()*(c.lineWidth*8)-(c.lineWidth*4));
var rndy = c.tool.last.y + Math.round(Math.random()*(c.lineWidth*8)-(c.lineWidth*4));
drawDot(rndx, rndy, 1, c.strokeStyle);
}
c.restore();
}
},
zoom: function() {
this.name = 'zoom';
this.status = 0;
//c.lastTool = c.tool.name;
this.down = function() {
zoomTo(c.selectedzoom);
}
this.move = function() { }
this.up = function() { }
},
picker: function() {
this.name = 'picker';
this.status = 0;
this.down = function(e) {
csel.drawImage(canvas, m.x, m.y, 1, 1, 0, 0, canvassel.width, canvassel.height);
var pat = c.createPattern(canvassel, 'repeat');
selCol2(pat, e);
//selTool(document.getElementById(c.lastTool));
}
this.move = function() { }
this.up = function() { }
},
floodfill: function() {
this.name = 'floodfill';
this.status = 0;
this.down = function(e) {
undoSave();
//var imgd = c.getImageData(0, 0, canvas.width, canvas.height);
var x = Math.round(m.x);
var y = Math.round(m.y);
var oldColor = getPixel(x, y);
if(!oldColor) { alert('Sorry, your browser doesn\'t support flood fill.'); return false; }
if(oldColor == c.strokeStyle) { return; }
iface.status.innerHTML = 'Filling... please wait.';
iface.xy.innerHTML = oldColor;
var stack = [{x:x, y:y}];
//var n = 0;
while(popped = stack.pop()) {
//n++;
//iface.txy.innerHTML = 'while'+n;
var x = popped.x;
var y1 = popped.y;
while(getPixel(x, y1) == oldColor && y1 >= 0) { y1--; }
y1++;
var spanLeft = false;
var spanRight = false;
while(getPixel(x, y1) == oldColor && y1 < canvas.height) {
//iface.xy.innerHTML = x+'/'+y1;
if(window.opera) {
co.setPixel(x, y1, c.strokeStyle);
} else {
//c.beginPath();
c.fillStyle = c.strokeStyle;
c.fillRect(x, y1, 1, 1);
//drawDot(x, y1, 1, c.strokeStyle, c);
//document.getElementById('info').innerHTML += '<br />'+x+'/'+y1;
}
if(!spanLeft && x > 0 && getPixel(x-1, y1) == oldColor) {
//break;
stack.push({x:x-1, y:y1});
spanLeft = true;
} else if(spanLeft && x > 0 && getPixel(x-1, y1) != oldColor) {
spanRight = false;
} else if(spanRight && x <= 0) { spanRight = false; }
if(!spanRight && x < canvas.width-1 && getPixel(x+1, y1) == oldColor) {
//break;
stack.push({x:x+1, y:y1});
spanRight = true;
} else if(spanRight && x < canvas.width-1 && getPixel(x+1, y1) != oldColor) {
spanRight = false;
} else if(spanRight && x >= canvas.width) { spanRight = false; }
y1++;
}
}
if(window.opera) {
co.lockCanvasUpdates(false);
co.updateCanvas();
}
//document.getElementById('info').innerHTML = check;
iface.status.innerHTML = 'Finished filling.';
}
this.move = function() { }
this.up = function() { }
},
select: function() {
this.name = 'select';
this.status = 0;
//c.lastTool = c.tool.name;
c.lineWidth = 1;
var dashed = new Image();
dashed.src = 'icons/dashed2.gif';
c.lastStrokeStyle = c.strokeStyle;
c.strokeStyle = c.createPattern(dashed, 'repeat');
c.strokeFill = 1;
c.beginPath();
this.down = function(e) {
if(this.status==0) { //starting select
var dashed = new Image(); //doing this here instead of in selTool to ignore user changing color
dashed.src = 'icons/dashed2.gif';
c.strokeStyle = c.createPattern(dashed, 'repeat');
activateTempCanvas();
this.start = { x:m.x, y:m.y }
this.status = 4;
} else if(this.status==3 || this.status==2) { //moving selection
if(intersects(m, this.start, this.dimension)) {
this.offset = { x:m.x-this.start.x, y:m.y-this.start.y }
if(this.status == 3 && !e.ctrlKey && !e.shiftKey) { //when first moving (and not in stamp mode), clear original pos and paint on tempcanvas
undoSave();
var pos = { x:m.x-this.offset.x, y:m.y-this.offset.y }
drawRectangle(pos.x-1, pos.y-1, pos.x+this.dimension.x, pos.y+this.dimension.y, null, ctemp);
ctemp.drawImage(canvassel, Math.floor(pos.x), Math.floor(pos.y));
c.fillRect(this.start.x-.5, this.start.y-.5, this.dimension.x, this.dimension.y);
}
this.status = 1;
} else { //starting new selection
if(this.status < 3) { //actually draw last moved selection to canvas TODO also do this when switching tools
c.drawImage(canvassel, Math.floor(this.start.x), Math.floor(this.start.y));
}
activateTempCanvas();
this.start = { x:m.x, y:m.y }
this.status = 4;
}
}
}
this.move = function(e) {
if(this.status==4) { //selecting
ctemp.clearRect(0, 0, canvastemp.width, canvastemp.height);
var constrained = { x:constrain(m.x, 0, canvas.width), y:constrain(m.y, 0, canvas.height-5) }
drawRectangle(this.start.x-1, this.start.y-1, constrained.x, constrained.y, null, ctemp);
iface.txy.innerHTML = Math.round(constrained.x-this.start.x)+'x'+Math.round(constrained.y-this.start.y);
} else if(this.status==1) { //moving selection
ctemp.clearRect(0, 0, canvastemp.width, canvastemp.height);
var pos = { x:m.x-this.offset.x, y:m.y-this.offset.y }
drawRectangle(pos.x-1, pos.y-1, pos.x+this.dimension.x, pos.y+this.dimension.y, null, ctemp);
ctemp.drawImage(canvassel, Math.floor(pos.x), Math.floor(pos.y));
if(e.shiftKey) { //dupli mode
c.drawImage(canvassel, Math.floor(pos.x), Math.floor(pos.y));
}
} else if(this.start) {
if(c.tool.status == 1 || (c.tool.dimension && intersects(m, c.tool.start, c.tool.dimension))) {
canvastemp.style.cursor = 'move';
} else {
canvastemp.style.cursor = '';
}
}
}
this.up = function(e) {
if(this.status == 4) { //finished selecting
this.status = 3;
this.dimension = { x:constrain(m.x, 0, canvas.width)-this.start.x,
y:constrain(m.y, 0, canvas.height)-this.start.y }
if(this.dimension.x == 0 && this.dimension.y == 0) { //nothing selected, abort
this.status = 0;
canvastemp.style.display='none';
csel.clearRect(0, 0, canvassel.width, canvassel.height);
} else { //save on selection canvas
csel.clearRect(0, 0, canvassel.width, canvassel.height);
if(this.dimension.x < 0) { this.start.x = this.start.x + this.dimension.x; this.dimension.x *= -1; } //correct for selections not drawn from top left
if(this.dimension.y < 0) { this.start.y = this.start.y + this.dimension.y; this.dimension.y *= -1; }
//todo check for >max
csel.drawImage(canvas, Math.floor(this.start.x), Math.floor(this.start.y), this.dimension.x, this.dimension.y, 0, 0, this.dimension.x, this.dimension.y);
csel.dimension = this.dimension;
}
iface.txy.innerHTML = '&nbsp;';
} else if(this.status == 1) { //finished moving selection
this.status = 2;
this.start = { x:m.x-this.offset.x, y:m.y-this.offset.y }
if(e.ctrlKey) { //stamp mode
c.drawImage(canvassel, Math.floor(this.start.x), Math.floor(this.start.y));
}
}
}
this.del = function() {
undoSave();
c.fillRect(this.start.x-.5, this.start.y-.5, this.dimension.x, this.dimension.y);
activateTempCanvas();
canvastemp.style.display = 'none';
this.status = 0;
}
this.all = function() {
csel.clearRect(0, 0, canvassel.width, canvassel.height);
csel.drawImage(canvas, 0, 0);
activateTempCanvas();
this.start = { x:0.5, y:0.5 }
this.dimension = { x:canvas.width, y:canvas.height }
ctemp.strokeRect(0.5, 0.5, canvas.width-1, canvas.height-1);
this.status = 3;
}
this.copy = function() {
csel.drawImage(canvas, Math.floor(this.start.x), Math.floor(this.start.y), this.dimension.x, this.dimension.y, 0, 0, this.dimension.x, this.dimension.y);
csel.dimension = this.dimension;
}
this.paste = function() {
activateTempCanvas();
ctemp.drawImage(canvassel, 0, 0);
this.status = 3;
this.start = { x:.5, y:.5 }
this.dimension = csel.dimension;
ctemp.strokeRect(this.start.x-.5, this.start.y-.5, this.dimension.x+.5, this.dimension.y+.5);
}
}
}
function getPixel(x, y) {
if(imgd || c.getImageData) {
//if(!imgd) {
// imgd = c.getImageData(0, 0, canvas.width, canvas.height);
// alert('getting img data');
// }
//var pos = (y*canvas.width+x)*4;
//if(imgd.data[pos]) {
// var col = 'rgb('+imgd.data[pos]+', '+imgd.data[pos+1]+', '+imgd.data[pos+2]+')'; //rgba: ', '+imgd.data[pos+3]+')';
// check += '<br />'+x+'/'+y+': '+col;
// //drawDot(x, y, 1, 'red', c);
// return col;
//} else {
return false;
//}
} else if (window.opera) {
if(!co) { co = canvas.getContext('opera-2dgame'); }
col = co.getPixel(x, y);
//check += '<br />'+x+'/'+y+': '+col;
return col;
} else {
return false;
}
}
function c_down(e) {
//handles mousedown on the canvas depending on tool selected
var source = e.currentTarget;
m = getxy(e, canvas);
if(c.tool.name != 'select' && c.tool.name != 'eraser' && c.tool.name != 'picker') { //no color switching for these
if(e.ctrlKey) { //ctrl: switch tert & stroke
var temp = c.tertStyle;
c.tertStyle = c.strokeStyle;
c.strokeStyle = temp;
}
if(e.button == 2 && c.tool.name != 'eraser') { //right: switch stroke & fill
var temp = c.strokeStyle;
c.strokeStyle = c.fillStyle;
c.fillStyle = temp;
}
}
c.tool.down(e);
c.moveTo(m.x, m.y); //?
//iface.status.innerHTML = 'c_down: '+m.x+'/'+m.y+' - status:'+c.tool.status+' - source:'+source.id+' - tool:'+c.tool.name;
return false;
}
function c_up(e) {
//handles mouseup on the canvas depending on tool selected
m = getxy(e, canvas);
e.stopPropagation();
if(iface.dragging || iface.resizing || c.resizing) { bodyUp(e); } //but not if dragging
c.tool.up(e);
if(c.tool.name != 'select' && c.tool.name != 'eraser' && c.tool.name != 'picker') { //no color switching for these
if(e.button == 2 && c.tool.name != 'eraser') { //right: switch stroke & fill back
var temp = c.fillStyle;
c.fillStyle = c.strokeStyle;
c.strokeStyle = temp;
}
if(e.ctrlKey) {
var temp = c.strokeStyle;
c.strokeStyle = c.tertStyle;
c.tertStyle = temp;
}
}
//iface.status.innerHTML = 'c_up - status: '+c.tool.status;
return false;
}
function c_move(e) {
m = getxy(e, canvas);
e.stopPropagation();
if(iface.dragging || iface.resizing || c.resizing) { bodyMove(e); } //don't stop propagation if dragging
if(c.tool.status > 0) {
c.tool.move(e);
}
if(c.tool.start && c.tool.status > 0) {
iface.xy.innerHTML = Math.round(c.tool.start.x)+', '+Math.round(c.tool.start.y);
} else {
iface.xy.innerHTML = Math.round(m.x)+', '+Math.round(m.y);
}
return false;
}
function c_out(e) {
//var source = e.currentTarget;
if(c && (c.tool.name=='pencil' || c.tool.name=='eraser' || c.tool.name=='brush') && c.tool.status==1) {
c.tool.disconnected = 1;
m = getxy(e, canvas);
c.tool.draw();
//iface.status.innerHTML = 'c_out: '+m.x+'/'+m.y+' '+c.fillStyle;
}
iface.xy.innerHTML = '&nbsp;';
}
function activateTempCanvas() {
//resets and shows overlay canvas
if(m) { ctemp.moveTo(m.x, m.y); } //copy context from main
ctemp.lineCap = c.lineCap;
ctemp.lineWidth = c.lineWidth;
ctemp.strokeStyle = c.strokeStyle;
ctemp.fillStyle = c.fillStyle;
ctemp.clearRect(0, 0, canvastemp.width, canvastemp.height); //clear
canvastemp.style.display='block'; //show
}
function undoSave() {
//sets an undo point
//cundo.clearRect(0, 0, canvas.width, canvas.height); //this doesn't help with the bg..
if(imgd) { imgd = null; }
if(canvas.width != canvasundo.width || canvas.height != canvasundo.height) {
canvasundo.width = canvas.width;
canvasundo.height = canvas.height;
}
cundo.drawImage(canvas, 0, 0);
}
function undoLoad() {
//reverts to last undo point
if(canvas.width != canvasundo.width || canvas.height != canvasundo.height) {
clipResize(canvasundo.width, canvasundo.height);
}
//ctemp.clearRect(0, 0, canvas.width, canvas.height);
ctemp.drawImage(canvas, 0, 0);
//c.clearRect(0, 0, canvas.width, canvas.height);
c.drawImage(canvasundo, 0, 0);
//cundo.clearRect(0, 0, canvas.width, canvas.height);
cundo.drawImage(canvastemp, 0, 0);
}
function windowDrag(e) {
iface.dragging = true;
var win = wsp.parentNode.parentNode.parentNode;
var pos = getpos(win);
c.start = { x:e.clientX-pos.x+1, y:e.clientY-pos.y+1 };
}
function windowResize(e, o) {
iface.resizing = true;
var pos = getpos(o);
c.start = { x:o.offsetWidth+3-(e.clientX-pos.x), y:o.offsetHeight+3-(e.clientY-pos.y) };
e.stopPropagation();
document.body.style.cursor = 'nw-resize';
}
function canvasResize(e) {
c.resizing = true;
document.body.style.cursor = 'nw-resize';
canvastemp.lastCursor = canvastemp.style.cursor;
canvastemp.style.cursor = 'nw-resize';
activateTempCanvas();
var dotted = new Image(); dotted.src = 'icons/dotted.gif';
ctemp.strokeStyle = ctemp.createPattern(dotted, 'repeat');
}
function clipResize(w, h) {
//resizes all the canvases by clipping/extending, moves resize handle
undoSave();
cundo.fillStyle = c.fillStyle; //save
canvas.width = canvastemp.width = canvassel.width = w;
canvas.height = canvastemp.height = canvassel.height = h;
canvas.style.width = canvastemp.style.width = w+'px';
canvas.style.height = canvastemp.style.height = h+'px';
var cresizer = document.getElementById('canvasresize');
cresizer.style.left = w+cresizer.offsetWidth+'px'; cresizer.style.top = h+cresizer.offsetHeight+'px';
c.fillStyle = cundo.fillStyle; //restore
c.fillRect(0, 0, canvas.width, canvas.height); //so that if expanding it's filled with bg col
c.drawImage(canvasundo, 0, 0);
}
function bodyMove(e) {
//lets the user move outside the canvas while drawing shapes and lines
if(c.tool.status > 0) { c_move(e); }
if(c.resizing) {
m = getxy(e, document.body);
var win = wsp.parentNode.parentNode.parentNode;
ctemp.clearRect(0, 0, canvastemp.width, canvastemp.height);
ctemp.strokeRect(0, 0, m.x, m.y); //dotted line
} else if(iface.dragging) {
m = getxy(e, document.body);
var win = wsp.parentNode.parentNode.parentNode;
win.style.left = e.clientX-c.start.x+'px';
win.style.top = e.clientY-c.start.y+'px';
} else if(iface.resizing) {
m = getxy(e, document.body);
var win = wsp.parentNode.parentNode.parentNode;
win.style.width = e.clientX-win.offsetLeft+c.start.x+3+'px';
win.style.height = e.clientY-win.offsetTop+c.start.y+3+'px';
//todo prevent selecting statusbar text.. how?
}
}
function bodyUp(e) {
//stops drawing even if mouseup happened outside canvas
//closes menus if clicking somewhere else
if(c.resizing) {
c.resizing = false; document.body.style.cursor = 'auto'; canvastemp.style.cursor = canvastemp.lastCursor;
m = getxy(e, wsp);
clipResize(m.x-3, m.y-3);
}
if(iface.dragging) { iface.dragging = false; }
if(iface.resizing) { iface.resizing = false; document.body.style.cursor = 'auto'; }
if(c.tool.name == 'select') { //cancel selection or finalize selection move
sel_cancel();
}
if(c && c.tool.name != 'polygon' && c.tool.status > 0) {
c_up(e);
}
if(document.getElementById('menubar').className=='open') {
document.getElementById('menubar').className='';
e.stopPropagation();
}
}
function drawDot(x, y, size, col, trg) {
x = Math.floor(x)+1; //prevent antialiasing of 1px dots
y = Math.floor(y)+1;
if(x>0 && y>0) {
if(!trg) { trg = c; }
if(col || size) { var lastcol = trg.fillStyle; var lastsize = trg.lineWidth; }
if(col) { trg.fillStyle = col; }
if(size) { trg.lineWidth = size; }
if(trg.lineCap == 'round') {
trg.arc(x, y, trg.lineWidth/2, 0, (Math.PI/180)*360, false);
trg.fill();
} else {
var dotoffset = (trg.lineWidth > 1) ? trg.lineWidth/2 : trg.lineWidth;
trg.fillRect((x-dotoffset), (y-dotoffset), trg.lineWidth, trg.lineWidth);
}
if(col || size) { trg.fillStyle = lastcol; trg.lineWidth = lastsize; }
}
}
function drawLine(x1, y1, x2, y2, mod, trg) {
if(trg.lineWidth % 2 == 0) { x1 = Math.floor(x1); y1 = Math.floor(y1); x2 = Math.floor(x2); y2 = Math.floor(y2); } //no antialiasing
trg.beginPath();
trg.moveTo(x1, y1);
if(mod) {
var dx = Math.abs(x2-x1);
var dy = Math.abs(y2-y1);
var dd = Math.abs(dx-dy);
if(dx > 0 && dy > 0 && dx != dy) {
if(dd < dx && dd < dy) { //diagonal
if(dx < dy) {
y2 = y1+(((y2-y1)/dy)*dx);
} else {
x2 = x1+(((x2-x1)/dx)*dy);
}
} else if(dx < dy) {
x2 = x1;
} else if(dy < dx) {
y2 = y1;
}
}
}
trg.lineTo(x2, y2);
trg.stroke();
trg.beginPath();
return { x:x2, y:y2 }
}
function drawEllipse(x1, y1, x2, y2, mod, trg) {
//bounding box. this maybe isn't the best idea?
var dx = Math.abs(x2-x1);
var dy = Math.abs(y2-y1);
if(mod && !(dx==dy)) { //shift held down: constrain
if(dx < dy) {
x2 = x1+(((x2-x1)/dx)*dy);
} else {
y2 = y1+(((y2-y1)/dy)*dx);
}
}
var KAPPA = 4 * ((Math.sqrt(2) -1) / 3);
var rx = (x2-x1)/2;
var ry = (y2-y1)/2;
var cx = x1+rx;
var cy = y1+ry;
trg.beginPath();
trg.moveTo(cx, cy - ry);
trg.bezierCurveTo(cx + (KAPPA * rx), cy - ry, cx + rx, cy - (KAPPA * ry), cx + rx, cy);
trg.bezierCurveTo(cx + rx, cy + (KAPPA * ry), cx + (KAPPA * rx), cy + ry, cx, cy + ry);
trg.bezierCurveTo(cx - (KAPPA * rx), cy + ry, cx - rx, cy + (KAPPA * ry), cx - rx, cy);
trg.bezierCurveTo(cx - rx, cy - (KAPPA * ry), cx - (KAPPA * rx), cy - ry, cx, cy - ry);
if(c.strokeFill == 1 || c.strokeFill == 3) { trg.stroke(); }
if(c.strokeFill == 2 || c.strokeFill == 3) { trg.fill(); }
}
function drawRectangle(x1, y1, x2, y2, mod, trg) {
var dx = Math.abs(x2-x1);
var dy = Math.abs(y2-y1);
if(mod && dx != dy) { //shift held down: constrain
if(dx < dy) {
y2 = y1+(((y2-y1)/dy)*dx);
} else {
x2 = x1+(((x2-x1)/dx)*dy);
}
}
if(c.strokeFill == 2 || trg.lineWidth % 2 == 0) { //no antialiasing
x1 = Math.floor(x1); y1 = Math.floor(y1); x2 = Math.floor(x2); y2 = Math.floor(y2);
}
trg.rect(x1, y1, (x2-x1), (y2-y1));
if(c.strokeFill == 2 || c.strokeFill == 3) { trg.fill(); }
if(c.strokeFill == 1 || c.strokeFill == 3) { trg.stroke(); }
}
function drawRounded(x1, y1, x2, y2, mod, trg) {
var dx = Math.abs(x2-x1);
var dy = Math.abs(y2-y1);
if(mod && dx != dy) { //shift held down: constrain
if(dx < dy) {
y2 = y1+(((y2-y1)/dy)*dx);
dy = dx;
} else {
x2 = x1+(((x2-x1)/dx)*dy);
dx = dy;
}
}
var dmin = (dx < dy) ? dx : dy;
var cornersize = (dmin/2 >= 15) ? 15 : dmin/2;
var xdir = (x2 > x1) ? cornersize : -1*cornersize;
var ydir = (y2 > y1) ? cornersize : -1*cornersize;
drawRounded2(trg, x1, x2, y1, y2, xdir, ydir);
if(c.strokeFill == 2 || c.strokeFill == 3) { trg.fill(); }
if(c.strokeFill == 1 || c.strokeFill == 3) { trg.stroke(); }
}
function drawRounded2(trg, x1, x2, y1, y2, xdir, ydir) {
trg.beginPath();
trg.moveTo(x1, y1+ydir);
trg.quadraticCurveTo(x1, y1, x1+xdir, y1);
trg.lineTo(x2-xdir, y1);
trg.quadraticCurveTo(x2, y1, x2, y1+ydir);
trg.lineTo(x2, y2-ydir);
trg.quadraticCurveTo(x2, y2, x2-xdir, y2);
trg.lineTo(x1+xdir, y2);
trg.quadraticCurveTo(x1, y2, x1, y2-ydir);
trg.closePath();
}
function constrain(n, min, max) {
if(n > max) return max;
if(n < min) return min;
return n;
}
function intersects(m, start, dim) {
//checks if m(x,y) is between start(x,y) and start+dim(x,y)
if( m.x >= start.x && m.y >= start.y &&
m.x <= (start.x+dim.x) && m.y <= (start.y+dim.y)) {
return true;
} else {
return false;
}
}
function selCol(o, e, context) {
//context because silly safari doesnt capture right click, apparently.. thus oncontextmenu
// alert(typeof(o));
col = (typeof(o) == 'string') ? o : o.style.backgroundColor;
selCol2(col, e, context);
// if(e) e.preventDefault();
}
function selCol2(col, e, context) {
if(e && e.ctrlKey) { //tertiary
var whichcanvas = document.getElementById('currcoltert');
c.tertStyle = col;
} else if(context == 1 || (e && e.button == 2)) { //right
var whichcanvas = document.getElementById('currcolback');
c.fillStyle = col;
ctemp.fillStyle=col;
if(c.tool.name=='eraser') { c.strokeStyle = col; }
} else {
var whichcanvas = document.getElementById('currcolfore');
c.strokeStyle=col;
ctemp.strokeStyle=col;
if(c.lastStrokeStyle) { c.lastStrokeStyle = col; } //allows color changing during select/eraser
}
if(whichcanvas) {
whichcontext = whichcanvas.getContext('2d');
whichcontext.fillStyle = col;
whichcontext.fillRect(0, 0, whichcanvas.width, whichcanvas.height);
}
if(e) e.preventDefault();
}
function selTool(o) {
c.tool.status = 0;
canvastemp.style.display='none';
var newtool = o.id;
iface.status.innerHTML=newtool;
document.getElementById('workspace').className = newtool;
//button highlighting
var toolbarbtns = document.getElementById('buttons').getElementsByTagName('li');
for(var i=0; i<toolbarbtns.length;i++) {
if(toolbarbtns[i].className == 'sel') { toolbarbtns[i].className=''; }
}
o.className = 'sel';
//reset color (after eraser and select)
if(c.lastStrokeStyle) { selCol(c.lastStrokeStyle); c.lastStrokeStyle = null }
if(c.tool.name == 'polygon') { c.tool.close(); }
c.lastTool = c.tool.name;
c.tool = new tool[newtool]();
var newtool = shareSettingsPanels(c.tool.name);
//settings panel switching
var settingpanels = document.getElementById('settings').getElementsByTagName('div');
for(var i=0; i<settingpanels.length;i++) {
if(settingpanels[i].style.display == 'block') { settingpanels[i].style.display='none'; }
}
if(document.getElementById(newtool+'-settings')) {
document.getElementById(newtool+'-settings').style.display = 'block';
if(newtool != 'zoom') { //cause this would switch back
var settingbtns = document.getElementById(newtool+'-settings').childNodes;
for(var i=0; i<settingbtns.length;i++) { //reapply last selection
if(settingbtns[i].className == 'sel') { settingbtns[i].onclick(); }
}
}
}
}
function selSetting(o, sett) {
c.tool.status = 0;
canvastemp.style.display='none';
var newtool = shareSettingsPanels(c.tool.name);
if(document.getElementById(newtool+'-settings')) {
var settingbtns = document.getElementById(newtool+'-settings').childNodes;
for(var i=0; i<settingbtns.length;i++) {
if(settingbtns[i].className == 'sel') { settingbtns[i].className=''; }
}
o.className = 'sel';
eval(sett);
}
if(c.tool.name=='zoom') { //switch back
selTool(document.getElementById(c.lastTool));
}
}
function shareSettingsPanels(tool) {
if(tool=='select' || tool=='text') { return 'select'; }
if(tool=='line' || tool=='curve') { return 'line'; }
if(tool=='rectangle' || tool=='polygon'|| tool=='ellipse' || tool=='rounded') { return 'shape'; }
if(tool=='ffselect' || tool=='select'|| tool=='text') { return 'trans'; }
return tool;
}
function buttonReset(o) {
iface.status.innerHTML='&nbsp;';
if(o.className=='down') { o.className=''; } //todo why isn't this working
}
function buttonDown(e, o) {
if(e.button != 2 && o.className != 'sel') { o.className='down'; } //not on rightclick
}
function save(onserver) {
if(canvas.toDataURL) {
var dataurl = canvas.toDataURL();
if(onserver == true) {
saveonline(dataurl);
} else {
overlay('Please right-click/save-as to save your drawing. <a href="#" onclick="overlay_hide()">close</a><br /><img src="'+dataurl+'" title="Please right-click/save-as" />');
}
} else {
alert('Sorry, your browser does not implement the toDataURL() method required to save images.');
}
}
function saveonline(dataurl) {
var req = null;
iface.status.innerHTML="Saving to server...";
if (window.XMLHttpRequest) {
req = new XMLHttpRequest();
//if (req.overrideMimeType) { req.overrideMimeType('text/xml'); }
} else {
return;
}
req.onreadystatechange = function() {
if(req.readyState == 4) {
if(req.status == 200) {
var response = req.responseText;
overlay('Your image has been saved as <input size="50" value="http://sigilmaster.com/sigils/'+response+'"> <a href="#" onclick="overlay_hide()">close</a><br /><img src="http://sigilmaster.com/sigils/'+response+'" />');
} else {
iface.status.innerHTML="Post error code " + req.status + " " + req.statusText;
}
}
};
req.open("POST", "save.php", true);
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
req.send('u='+dataurl);
}
function overlay(content) {
var obg = document.getElementById('overlaybg');
obg.style.display='block';
var o = document.getElementById('overlay');
o.innerHTML = content;
o.style.display = 'block';
}
function overlay_hide() {
var o = document.getElementById('overlay');
o.innerHTML = '';
o.style.display = 'none';
document.getElementById('overlaybg').style.display = 'none';
}
function menuOpen(e, o) {
iface.txy.innerHTML = o.parentNode.className; //todo remove after fixing menu bug
o.parentNode.className = (o.parentNode.className != 'open') ? 'open' : '';
e.stopPropagation();
}
var paint = {
open: function(url) {
if(url.indexOf('.') == -1) { url += '.png' }
if(url.indexOf('://') == -1) { url = 'http://sigilmaster.com/'+url; }
var src = (url.indexOf('http://sigilmaster.com/') > -1) ? url.replace('http://sigilmaster.com', '') : 'open.php?u='+url;
var img = document.createElement('img');
img.src = src;
img.style.visibility='none';
iface.status.innerHTML = 'Opening '+src+'...';
img.onload = function() {
clipResize(this.width, this.height);
c.drawImage(this, 0, 0);
iface.status.innerHTML = 'Opened '+url+'.';
}
img.onerror = function() {
alert('Error opening '+this.src);
iface.status.innerHTML = '';
}
}
}
// 1337, dude.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment