Determine bounds of shape / graphics drawn into a Canvas - javascript

I have a simple HTML5 Canvas example that lets the user draw paths onto a canvas. Is there any way to determine the rectangular bounds of the path / shape that was drawn? (i.e., what is the width, height of the rectangular region surrounding the path).
I realize I could do the math while the shape is being drawn to figure out the bounds, but I wanted to see if there was an easier / built in way.

I assume you are using lineTos the only way I could think of would be to keep a min/max stored for the height and width as the user is drawing paths. Other than that the only way to pull back info from the canvas would be to use getImageData which will only give you raw pixel information.
Quick example showing this
var ctx = document.getElementById("canvas").getContext("2d");
var xMin, xMax, yMin, yMax;
// These are set to where the path starts, i start them at 10,10
xMin = xMax = 10;
yMin = yMax = 10;
ctx.beginPath();
ctx.moveTo(10,10);
for(var i = 0; i <10; i++){
var x = Math.floor(Math.random()*150),
y = Math.floor(Math.random()*150);
ctx.lineTo(x,y);
if(x < xMin){
xMin = x;
}
if(x > xMax){
xMax = x;
}
if(y < yMin){
yMin = y;
}
if(y > yMax){
yMax = y;
}
}
ctx.strokeStyle = "rgb(0,0,0)";
ctx.stroke();
ctx.closePath();
ctx.strokeStyle = "rgb(255,0,0)";
ctx.strokeRect(xMin,yMin,xMax - xMin,yMax - yMin);
#canvas{
width: 300px;
height: 300px;
}
<canvas id="canvas"></canvas>
note I just create a bunch of random points. The main thing to remember is set the min/max vals to the coords of the first path a user creates.
I guess you knew that though, so the real answer is no there is unfortunately no built in way currently to do it..

Although you have to track it yourself, I would suggest wrapping it up in reusable functionality. Here's a minimal example, tracking it only for moveTo and lineTo. See the live example here: http://phrogz.net/tmp/canvas_bounding_box.html
function trackBBox( ctx ){
var begin = ctx.beginPath;
ctx.beginPath = function(){
this.minX = this.minY = 99999999999;
this.maxX = this.maxY = -99999999999;
return begin.call(this);
};
ctx.updateMinMax = function(x,y){
if (x<this.minX) this.minX = x;
if (x>this.maxX) this.maxX = x;
if (y<this.minY) this.minY = y;
if (y>this.maxY) this.maxY = y;
};
var m2 = ctx.moveTo;
ctx.moveTo = function(x,y){
this.updateMinMax(x,y);
return m2.call(this,x,y);
};
var l2 = ctx.lineTo
ctx.lineTo = function(x,y){
this.updateMinMax(x,y);
return l2.call(this,x,y);
};
ctx.getBBox = function(){
return {
minX:this.minX,
maxX:this.maxX,
minY:this.minY,
maxY:this.maxY,
width:this.maxX-this.minX,
height:this.maxY-this.minY
};
};
}
...
var ctx = myCanvas.getContext("2d");
// Cause the canvas to track its own bounding box for each path
trackBBox(ctx);
ctx.beginPath();
ctx.moveTo(40,40);
for(var i=0; i<10; i++) ctx.lineTo(Math.random()*600,Math.random()*400);
// Find the bounding box of the current path
var bbox = ctx.getBBox();
ctx.strokeRect(bbox.minX,bbox.minY,bbox.width,bbox.height);

Inspired by #Phrogz's answer, the answer from Calculate bounding box of arbitrary pixel-based drawing, and his two slightly different demos http://phrogz.net/tmp/canvas_bounding_box.html and http://phrogz.net/tmp/canvas_bounding_box2.html, here is a version not using the alpha channel (it did not work in my case) but just using a comparison to white color.
function contextBoundingBox(ctx){
var w=ctx.canvas.width,h=ctx.canvas.height;
var data = ctx.getImageData(0,0,w,h).data;
var x,y,minX,minY,maxY,maxY;
o1: for (y=h;y--;) for (x=w;x--;) if ((data[(w*y+x)*4] != 255) && (data[(w*y+x)*4+1] != 255) && (data[(w*y+x)*4+2] != 255)) { maxY=y; break o1 }
o2: for (x=w;x--;) for (y=maxY+1;y--;) if ((data[(w*y+x)*4] != 255) && (data[(w*y+x)*4+1] != 255) && (data[(w*y+x)*4+2] != 255)) { maxX=x; break o2 }
o3: for (x=0;x<=maxX;++x) for (y=maxY+1;y--;) if ((data[(w*y+x)*4] != 255) && (data[(w*y+x)*4+1] != 255) && (data[(w*y+x)*4+2] != 255)) { minX=x; break o3 }
o4: for (y=0;y<=maxY;++y) for (x=minX;x<=maxX;++x) if ((data[(w*y+x)*4] != 255) && (data[(w*y+x)*4+1] != 255) && (data[(w*y+x)*4+2] != 255)) { minY=y; break o4 }
return {x:minX,y:minY,maxX:maxX,maxY:maxY,w:maxX-minX,h:maxY-minY};
}

Related

Javascript 3d Terrain Without Three.js

I have searched around but I can't find anything like what I'm trying to do that doesn't use Three.js in some way (I can't use Three.js because my computer is too old to support Webgl). Here's what I've got so far:
HTML:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="terrain.js"></script>
<title>Terrain</title>
</head>
<body>
<canvas id="canvas" height="400" width="400"></canvas>
</body>
</html>
Javascript:
var canvas, ctx, row1 = [], row2 = [], intensity = 15, width = 20, height = 20, centery = 200, centerx = 200, minus, delta = 1.6, nu = .02;
window.onload = function() {
canvas = document.getElementById('canvas'), ctx = canvas.getContext('2d');
ctx.lineStyle = '#000'
for (var i = 0; i < height; i++) {
row2 = [];
minus = 200
for (var j = 0; j < width; j++) {
row2[j] = {
x: centerx - (minus * (delta * (nu * i))),
y: Math.floor(Math.random() * intensity) + (height * i)
}
minus -= height;
}
ctx.beginPath();
ctx.moveTo(row2[0].x,row2[0].y)
for (var k = 1; k < row2.length; k++) {
ctx.lineTo(row2[k].x,row2[k].y)
if (k == row2.length) {ctx.clostPath()}
}
ctx.stroke();
if (row1[0] && row2[0]) {
for (var l = 0; l < row2.length; l++) {
ctx.beginPath();
ctx.moveTo(row2[l].x,row2[l].y)
ctx.lineTo(row1[l].x,row1[l].y)
ctx.closePath();
ctx.stroke();
}
}
row1 = row2;
}
}
Currently, the result looks like a Christmas tree but I want it to look more like actual 3d wireframe terrain.
3D wire frame basics
3D can be done on any systems that can move pixels. Thought not by dedicated hardware Javascript can do alright if you are after simple 3d.
This answers shows how to create a mesh, rotate and move it, create a camera and move it, and project the whole lot onto the 2D canvas using simple moveTo, and lineTo calls.
This answer is a real rush job so apologies for the typos (if any) and messy code. Will clean it up in the come few days (if time permits). Any questions please do ask in the comments.
Update
I have not done any basic 3D for some time so having a little fun I have added to the answer with more comments in the code and added some extra functionality.
vec3 now has normalise, dot, cross functions.
mat now has lookat function and is ready for much more if needed.
mesh now maintains its own world matrix
Added box, and line that create box and line meshs
Created a second vector type vec3S (S for simple) that is just coordinates no functionality
Demo now shows how to add more objects, position them in the scene, use a lookat transform
Details about the code.
The code below is the basics of 3D. It has a mesh object to create objects out of 3D points (vertices) connected via lines.
Simple transformation for rotating, moving and scaling a model so it can be placed in the scene.
A very very basic camera that can only look forward, move up,down, left,right, in and out. And the focal length can be changed.
Only for lines as there is no depth sorting.
The demo does not clip to the camera front plane, but rather just ignores lines that have any part behind the camera;
You will have to work out the rest from the comments, 3D is a big subject and any one of the features is worth a question / answer all its own.
Oh and coordinates in 3D are origin in center of canvas. Y positive down, x positive right, and z positive into the screen. projection is basic so when you have perspective set to 400 than a object at 400 units out from camera will have a one to one match with pixel size.
var ctx = canvas.getContext("2d");
// some usage of vecs does not need the added functionality
// and will use the basic version
const vec3Basic = { x : 0, y : 0, z: 0};
const vec3Def = {
// Sets the vector scalars
// Has two signatures
// setVal(x,y,z) sets vector to {x,y,z}
// setVal(vec) set this vector to vec
setVal(x,y = x.y,z = x.z + (x = x.x) * 0){
this.x = x;
this.y = y;
this.z = z;
},
// subtract v from this vector
// Has two signatures
// setVal(v) subtract v from this returning a new vec3
// setVal(v,vec) subtract v from this returning result in retVec
sub(v,retVec = vec3()){
retVec.x = this.x - v.x;
retVec.y = this.y - v.y;
retVec.z = this.z - v.z;
return retVec;
},
// Cross product of two vectors this and v.
// Cross product can be thought of as get the vector
// that is perpendicular to the plane described by the two vector we are crossing
// Has two signatures
// cross(vec); // returns a new vec3 as the cross product of this and vec
// cross(vec, retVec); // set retVec as the cross product
cross (v, retVec = vec3()){
retVec.x = this.y * v.z - this.z * v.y;
retVec.y = this.z * v.x - this.x * v.z;
retVec.z = this.x * v.y - this.y * v.x;
return retVec;
},
// Dot product
// Dot product of two vectors if both normalized can be thought of as finding the cos of the angle
// between two vectors. If not normalised the dot product will give you < 0 if v points away from
// the plane that this vector is perpendicular to, if > 0 the v points in the same direction as the
// plane perpendicular to this vector. if 0 then v is at 90 degs to the plane this is perpendicular to
// Using vector dot on its self is the same as getting the length squared
// dot(vec3); // returns a number as a float
dot (v){ return this.x * v.x + this.y * v.y + this.z * this.z },
// normalize normalizes a vector. A normalized vector has length equale to 1 unit
// Has two signitures
// normalise(); normalises this vector returning this
// normalize(retVec); normalises this vector but puts the normalised vector in retVec returning
// returning retVec. Thiis is unchanged.
normalize(retVec = this){
// could have used len = this.dot(this) but for speed all functions will do calcs internaly
const len = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
// it is assumed that all vector are valid (have length) so no test is made to avoid
// the divide by zero that will happen for invalid vectors.
retVec.x = this.x / len;
retVec.y = this.y / len;
retVec.z = this.z / len;
}
}
// Created as a singleton to close over working constants
const matDef = (()=>{
// to seed up vector math the following closed over vectors are used
// rather than create and dispose of vectors for every operation needing them
// Currently not used
const V1 = vec3();
return {
// The matrix is just 3 pointers one for each axis
// They represent the direction and scale in 3D of each axis
// when you transform a point x,y,z you move x along the x axis,
// then y along y and z along the z axis
xAxis : null,
yAxis : null,
zAxis : null,
// this is a position x,y,z and represents where in 3D space an objects
// center coordinate (0,0,0) will be. It is simply added to a point
// after it has been moved along the 3 axis.
pos : null,
// This function does most of the 3D work in most 3D environments.
// It rotates, scales, translates, and a whole lot more.
// It is a cut down of the full 4 by 4 3D matrix you will find in
// Libraries like three.js
transformVec3(vec,retVec = {}){
retVec.x = vec.x * this.xAxis.x + vec.y * this.yAxis.x + vec.z * this.zAxis.x + this.pos.x;
retVec.y = vec.x * this.xAxis.y + vec.y * this.yAxis.y + vec.z * this.zAxis.y + this.pos.y;
retVec.z = vec.x * this.xAxis.z + vec.y * this.yAxis.z + vec.z * this.zAxis.z + this.pos.z;
return retVec;
},
// resets the matrix
identity(){ // default matrix
this.xAxis.setVal(1,0,0); // x 1 unit long in the x direction
this.yAxis.setVal(0,1,0); // y 1 unit long in the y direction
this.zAxis.setVal(0,0,1); // z 1 unit long in the z direction
this.pos.setVal(0,0,0); // and position at the origin.
},
init(){ // need to call this before using due to the way I create these
// objects.
this.xAxis = vec3(1,0,0);
this.yAxis = vec3(0,1,0);
this.zAxis = vec3(0,0,1);
this.pos = vec3(0,0,0);
return this; // must have this line for the constructor function to return
},
setRotateY(amount){
var x = Math.cos(amount);
var y = Math.sin(amount);
this.xAxis.x = x;
this.xAxis.y = 0;
this.xAxis.z = y;
this.zAxis.x = -y;
this.zAxis.y = 0;
this.zAxis.z = x;
},
// creates a look at transform from the current position
// point is a vec3.
// No check is made to see if look at is at pos which will invalidate this matrix
// Note scale is lost in this operation.
lookAt(point){
// zAxis along vector from pos to point
this.pos.sub(point,this.zAxis).normalize();
// use y as vertical reference
this.yAxis.x = 0;
this.yAxis.y = 1;
this.yAxis.z = 0;
// get x axis perpendicular to the plane described by z and y axis
// need to normalise as z and y axis may not be at 90 deg
this.yAxis.cross(this.zAxis,this.xAxis).normalize();
// Get the y axis that is perpendicular to z and x axis
// Normalise is not really needed but rounding errors can be problematic
// so the normalise just fixes some of the rounding errors.
this.zAxis.cross(this.xAxis,this.yAxis).normalize();
},
}
})();
// Mesh object has buffers for the
// model as verts
// transformed mesh as tVerts
// projected 2D verts as dVerts (d for display)
// An a array of lines. Each line has two indexes that point to the
// vert that define their ends.
// Buffers are all preallocated to stop GC slowing everything down.
const meshDef = {
addVert(vec){
this.verts.push(vec);
// vec3(vec) in next line makes a copy of the vec. This is important
// as using the same vert in the two buffers will result in strange happenings.
this.tVerts.push(vec3S(vec)); // transformed verts pre allocated so GC does not bite
this.dVerts.push({x:0,y:0}); // preallocated memory for displaying 2d projection
// when x and y are zero this means that it is not visible
return this.verts.length - 1;
},
addLine(index1,index2){
this.lines.push(index1,index2);
},
transform(matrix = this.matrix){
for(var i = 0; i < this.verts.length; i++){
matrix.transformVec3(this.verts[i],this.tVerts[i]);
}
},
eachVert(callback){
for(var i = 0; i < this.verts.length; i++){
callback(this.tVerts[i],i);
}
},
eachLine(callback){
for(var i = 0; i < this.lines.length; i+= 2){
var ind1 = this.lines[i];
var v1 = this.dVerts[ind1]; // get the start
if(v1.x !== 0 && v1.y !== 0){ // is valid
var ind2 = this.lines[i+ 1]; // get end of line
var v2 = this.dVerts[ind2];
if(v2.x !== 0 && v2.y !== 0){ // is valid
callback(v1,v2);
}
}
}
},
init(){ // need to call this befor using
this.verts = [];
this.lines = [];
this.dVerts = [];
this.tVerts = [];
this.matrix = mat();
return this; // must have this line for the construtor function to return
}
}
const cameraDef = {
projectMesh(mesh){ // create a 2D mesh
mesh.eachVert((vert,i)=>{
var z = (vert.z + this.position.z);
if(z < 0){ // is behind the camera then ignor it
mesh.dVerts[i].x = mesh.dVerts[i].y = 0;
}else{
var s = this.perspective / z;
mesh.dVerts[i].x = (vert.x + this.position.x) * s;
mesh.dVerts[i].y = (vert.y + this.position.y) * s;
}
})
},
drawMesh(mesh){ // renders the 2D mesh
ctx.beginPath();
mesh.eachLine((v1,v2)=>{
ctx.moveTo(v1.x,v1.y);
ctx.lineTo(v2.x,v2.y);
})
ctx.stroke();
}
}
// vec3S creates a basic (simple) vector
// 3 signatures
//vec3S(); // return vec 1,0,0
//vec3S(vec); // returns copy of vec
//vec3S(x,y,z); // returns {x,y,z}
function vec3S(x = {x:1,y:0,z:0},y = x.y ,z = x.z + (x = x.x) * 0){ // a 3d point
return Object.assign({},vec3Basic,{x, y, z});
}
// vec3S creates a basic (simple) vector
// 3 signatures
//vec3S(); // return vec 1,0,0
//vec3S(vec); // returns copy of vec
//vec3S(x,y,z); // returns {x,y,z}
function vec3(x = {x:1,y:0,z:0},y = x.y ,z = x.z + (x = x.x) * 0){ // a 3d point
return Object.assign({},vec3Def,{x,y,z});
}
function mat(){ // matrix used to rotate scale and move a 3d point
return Object.assign({},matDef).init();
}
function mesh(){ // this is for storing objects as points in 3d and lines conecting points
return Object.assign({},meshDef).init();
}
function camera(perspective,position){ // this is for displaying 3D
return Object.assign({},cameraDef,{perspective,position});
}
// grid is the number of grids x,z and size is the overal size for x
function createLandMesh(gridx,gridz,size,maxHeight){
var m = mesh(); // create a mesh
var hs = size/2 ;
var step = size / gridx;
for(var z = 0; z < gridz; z ++){
for(var x = 0; x < gridx; x ++){
// create a vertex. Y is random
m.addVert(vec3S(x * step - hs, (Math.random() * maxHeight), z * step-hs)); // create a vert
}
}
for(var z = 0; z < gridz-1; z ++){
for(var x = 0; x < gridx-1; x ++){
if(x < gridx -1){ // dont go past end
m.addLine(x + z * gridx,x + 1 + z * gridx); // add line across
}
if(z < gridz - 1){ // dont go past end
m.addLine(x + z * (gridx-1),x + 1 + (z + 1) * (gridx-1));
}
}
}
return m;
}
function createBoxMesh(size){
var s = size / 2;
var m = mesh(); // create a mesh
// add bottom
m.addVert(vec3S(-s,-s,-s));
m.addVert(vec3S( s,-s,-s));
m.addVert(vec3S( s, s,-s));
m.addVert(vec3S(-s, s,-s));
// add top verts
m.addVert(vec3S(-s,-s, s));
m.addVert(vec3S( s,-s, s));
m.addVert(vec3S( s, s, s));
m.addVert(vec3S(-s, s, s));
// add lines
/// bottom lines
m.addLine(0,1);
m.addLine(1,2);
m.addLine(2,3);
m.addLine(3,0);
/// top lines
m.addLine(4,5);
m.addLine(5,6);
m.addLine(6,7);
m.addLine(7,4);
// side lines
m.addLine(0,4);
m.addLine(1,5);
m.addLine(2,6);
m.addLine(3,7);
return m;
}
function createLineMesh(v1 = vec3S(),v2 = vec3S()){
const m = mesh();
m.addVert(v1);
m.addVert(v2);
m.addLine(0,1);
return m;
}
//Create a land mesh grid 20 by 20 and 400 units by 400 units in size
var land = createLandMesh(20,20,400,20); // create a land mesh
var box = createBoxMesh(50);
var box1 = createBoxMesh(25);
var line = createLineMesh(); // line conecting boxes
line.tVerts[0] = box.matrix.pos; // set the line transformed tVect[0] to box matrix.pos
line.tVerts[1] = box1.matrix.pos; // set the line transformed tVect[0] to box1 matrix.pos
var cam = camera(200,vec3(0,0,0)); // create a projection with focal len 200 and at 0,0,0
box.matrix.pos.setVal(0,-100,400);
box1.matrix.pos.setVal(0,-100,400);
land.matrix.pos.setVal(0,100,300); // move down 100, move away 300
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center of canvas
var ch = h / 2;
function update(timer){
// next section just maintains canvas size and resets state and clears display
if (canvas.width !== innerWidth || canvas.height !== innerHeight) {
cw = (w = canvas.width = innerWidth) /2;
ch = (h = canvas.height = innerHeight) /2;
}
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.fillStyle = "black";
ctx.fillRect(0,0,canvas.width,canvas.height);
// end of standard canvas maintenance
// render from center of canvas by setting canvas origin to center
ctx.setTransform(1,0,0,1,canvas.width / 2,canvas.height / 2)
land.matrix.setRotateY(timer/1000); // set matrix to rotation position
land.transform();
// move the blue box
var t = timer/1000;
box1.matrix.pos.setVal(Math.sin(t / 2.1) * 100,Math.sin( t / 3.2) * 100, Math.sin(t /5.3) * 90+300);
// Make the cyan box look at the blue box
box.matrix.lookAt(box1.matrix.pos);
// Transform boxes from local to world space
box1.transform();
box.transform();
// set camera x,y pos to mouse pos;
cam.position.x = mouse.x - cw;
cam.position.y = mouse.y - ch;
// move in and out
if (mouse.buttonRaw === 1) { cam.position.z -= 1 }
if (mouse.buttonRaw === 4) {cam.position.z += 1 }
// Converts mesh transformed verts to 2D screen coordinates
cam.projectMesh(land);
cam.projectMesh(box);
cam.projectMesh(box1);
cam.projectMesh(line);
// Draw each mesh in turn
ctx.strokeStyle = "#0F0";
cam.drawMesh(land);
ctx.strokeStyle = "#0FF";
cam.drawMesh(box);
ctx.strokeStyle = "#00F";
cam.drawMesh(box1);
ctx.strokeStyle = "#F00";
cam.drawMesh(line);
ctx.setTransform(1,0,0,1,cw,ch / 4);
ctx.font = "20px arial";
ctx.textAlign = "center";
ctx.fillStyle = "yellow";
ctx.fillText("Move mouse to move camera. Left right mouse move in out",0,0)
requestAnimationFrame(update);
}
requestAnimationFrame(update);
// A mouse handler from old lib of mine just to give some interaction
// not needed for the 3d
var mouse = (function () {
var m; // alias for mouse
var mouse = {
x : 0, y : 0, // mouse position
buttonRaw : 0,
buttonOnMasks : [0b1, 0b10, 0b100], // mouse button on masks
buttonOffMasks : [0b110, 0b101, 0b011], // mouse button off masks
bounds : null,
event(e) {
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left - scrollX;
m.y = e.pageY - m.bounds.top - scrollY;
if (e.type === "mousedown") { m.buttonRaw |= m.buttonOnMasks[e.which - 1] }
else if (e.type === "mouseup") { m.buttonRaw &= m.buttonOffMasks[e.which - 1] }
e.preventDefault();
},
start(element) {
m.element = element === undefined ? document : element;
"mousemove,mousedown,mouseup".split(",").forEach(name => document.addEventListener(name, mouse.event) );
document.addEventListener("contextmenu", (e) => { e.preventDefault() }, false);
return mouse;
},
}
m = mouse;
return mouse;
})().start(canvas);
canvas { position:absolute; top : 0px; left : 0px;}
<canvas id="canvas"></canvas>

Javascript - get actual rendered font height

In my on-the-fly editor tool I would really appreciate to get actual rendered height of the text / font - (I do not mean just getting CSS font-size, neither computed nor preset).
Is this achieveable in javascript?
If not directly, is possible something as rendering font in canvas the same way as it is rendered as regular text - and then finding out?
EDIT - my "dev" solution: Based on suggested links I've built a little pure-javascript code, that goes through pixels in canvas and analyses whether the pixel is white or not and acts accordingly, it is hardly a developer version of a code - just outputs few useful info and shows how to access computed data - http://jsfiddle.net/DV9Bw/1325/
HTML:
<canvas id="exampleSomePrettyRandomness" width="200" height="60"></canvas>
<div id="statusSomePrettyRandomness"></div>
JS:
function findPos(obj) {
var curleft = 0, curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
return { x: curleft, y: curtop };
}
return undefined;
}
var status = document.getElementById('statusSomePrettyRandomness');
var example = document.getElementById('exampleSomePrettyRandomness');
var context = example.getContext('2d');
context.fillStyle = "rgb(255,255,255)";
context.fillRect(0, 0, 200, 200);
context.fillStyle = "rgb(0,0,0)";
context.font = "30px Arial";
context.fillText("Hello World",0,30);
var pos = findPos(example);
var x = example.pageX - pos.x;
var y = example.pageY - pos.y;
var foundTop = false;
xPos = 0;
yPos = 0;
topY = -1;
bottomY = -1;
var fuse = 1000;
while( fuse-- > 0 ){
//status.innerHTML += yPos+"<br>";
if( yPos == (example.offsetHeight - 2) ){
xPos++;
yPos = 0;
continue;
}
var data = context.getImageData(xPos, yPos, 1, 1).data;
if( ! foundTop ){
if( (data[0] != 255) && (data[1] != 255) && (data[2] != 255) ){
topY = yPos;
status.innerHTML += "<br>Found top: "+topY+" X:"+xPos+" Color: rgba("+data[0]+","+data[1]+","+data[2]+")"+"<br>";
foundTop = true;
}
} else {
if( (data[0] == 255) && (data[1] == 255) && (data[2] == 255) ){
bottomY = yPos;
status.innerHTML += "<br>Found bottom: "+bottomY+" X:"+xPos+"<br>";
break;
}
}
yPos++;
if( yPos > example.offsetHeight ){
status.innerHTML += ""
+"Y overflow ("+yPos+">"+example.offsetHeight+")"
+" - moving X to "+xPos
+" - reseting Y to "+yPos
+"<br>"
;
xPos++;
yPos = 0;
}
}
status.innerHTML += "Fuse:"+fuse+", Top:"+topY+", Bottom: "+bottomY+"<br>";
status.innerHTML += "Font height should be: "+(bottomY-topY)+"<br>";
EDIT 2: Why this is not a duplicate: My question is about really just real rendered height of a font or a letter, "possible duplicate" is about how much space do you need to print a text, answers provided there don't answer my exact problem anyways.
I am not aware of any method that would return the height of a text such as measureText (which does currently return the width).
However, in theory you can simply draw your text in the canvas then trim the surrounding transparent pixels then measure the canvas height..
Here is an example (the height will be logged in the console):
// Create a blank canvas (by not filling a background color).
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// Fill it with some coloured text.. (black is default)
ctx.font = "48px serif";
ctx.textBaseline = "hanging";
ctx.fillText("Hello world", 0, 0);
// Remove the surrounding transparent pixels
// result is an actual canvas element
var result = trim(canvas);
// you could query it's width, draw it, etc..
document.body.appendChild(result);
// get the height of the trimmed area
console.log(result.height);
// Trim Canvas Pixels Method
// https://gist.github.com/remy/784508
function trim(c) {
var ctx = c.getContext('2d'),
// create a temporary canvas in which we will draw back the trimmed text
copy = document.createElement('canvas').getContext('2d'),
// Use the Canvas Image Data API, in order to get all the
// underlying pixels data of that canvas. This will basically
// return an array (Uint8ClampedArray) containing the data in the
// RGBA order. Every 4 items represent one pixel.
pixels = ctx.getImageData(0, 0, c.width, c.height),
// total pixels
l = pixels.data.length,
// main loop counter and pixels coordinates
i, x, y,
// an object that will store the area that isn't transparent
bound = { top: null, left: null, right: null, bottom: null };
// for every pixel in there
for (i = 0; i < l; i += 4) {
// if the alpha value isn't ZERO (transparent pixel)
if (pixels.data[i+3] !== 0) {
// find it's coordinates
x = (i / 4) % c.width;
y = ~~((i / 4) / c.width);
// store/update those coordinates
// inside our bounding box Object
if (bound.top === null) {
bound.top = y;
}
if (bound.left === null) {
bound.left = x;
} else if (x < bound.left) {
bound.left = x;
}
if (bound.right === null) {
bound.right = x;
} else if (bound.right < x) {
bound.right = x;
}
if (bound.bottom === null) {
bound.bottom = y;
} else if (bound.bottom < y) {
bound.bottom = y;
}
}
}
// actual height and width of the text
// (the zone that is actually filled with pixels)
var trimHeight = bound.bottom - bound.top,
trimWidth = bound.right - bound.left,
// get the zone (trimWidth x trimHeight) as an ImageData
// (Uint8ClampedArray of pixels) from our canvas
trimmed = ctx.getImageData(bound.left, bound.top, trimWidth, trimHeight);
// Draw back the ImageData into the canvas
copy.canvas.width = trimWidth;
copy.canvas.height = trimHeight;
copy.putImageData(trimmed, 0, 0);
// return the canvas element
return copy.canvas;
}
<canvas id="canvas"></canvas>
Image Data API: https://developer.mozilla.org/en-US/docs/Web/API/ImageData

Javascript not loading with HTML

I've been trying to put together a simple maze game using HTML5 and Javascript. I can successfully load the HTML and CSS content onto the page, but no matter what I try, I can't get the JS to load. It's definitely saved as a .html file and i've only been using Sublime text to put it together (but I wouldn't have thought that would have an affect anyway). Just a bit stumped really, so I thought it must be something I've missed in my code. I wasn't sure if I've missed something?
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title> Maze Game </title>
</head>
<style>
canvas {
border: 8px double navy;
background: white;
}
img {
display: none;
}
button {
padding: 3px;
}
</style>
<body>
<canvas id="canvas"> </canvas>
<img id="sprite" src="sprite.png">
<script>
//these define the global variables for the canvas and the drawing context
var canvas;
var context;
var x = 0;
var y = 0; //positioning of the sprite
var dx = 0;
var dy = 0; //momentum of the sprite at start
window.onload = function() {
//setting up the canvas
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
//Draws the maze background
drawMaze("maze.png", 268, 5);
//On key press, run the following function
window.onkeydown = processKey;
};
var x = 0;
var y = 0;
function drawMaze(mazeFile, Xstart, Ystart) {
//This loads the maze picture in
dx = 0;
dy = 0; //if the face is already moving, stop it
var imgMaze = new Image();
imgMaze.onLoad = function() {
canvas.width = imgMaze.width;
canvas.height = imgMaze.height;
//Draws the maze onto the canvas
context.drawImage(imgMaze, 0, 0);
//draws the sprite and positions
x = Xstart;
y = Ystart;
var imgSprite = document.getElementById("sprite");
context.drawImage(imgSprite, x, y);
context.stroke();
//sets a short timer for the next frame to be drawn in (10ms)
setTimeout("drawFrame()", 10);
};
imgMaze.src = mazeFile;
}
function processKey(e) { //e needs to be used for event handling
//stop the sprite if it's already moving - enables collision
var dx = 0;
var dy = 0;
//condition for the Up arrow being pressed
if (e.keyCode == 38) {
dy = -1;
}
//condition for the Left arrow being pressed
if (e.keyCode == 37) {
dx = -1;
}
//condition for the Down arrow being pressed
if (e.keyCode == 40) {
dy = 1;
}
//condition for the Right arrow being pressed
if (e.keyCode == 39) {
dx = 1;
}
}
function drawFrame() {
if (dx != 0 || dy != 0) {
context.beginPath();
context.fillStyle = "rgb(254,244,207)";
context.rect(x, y, 15, 15);
context.fill
x += dx;
y += dy;
if (checkForCollision()) {
(dx/y = 0)
x -= dx;
y -= dy;
dx = 0;
dy = 0;
}
//Now we can finally draw the sprite!
var imgSprite = document.getElementById("sprite");
context.drawImage(imgSprite, x, y);
if (y > (canvas.height - 17)) {
alert("Congratulations! You made it!");
return;
}
}
timer = setTimeout(drawFrame, 10);
}
var imageData = context.getImageData(0, 0, 100, 50);
var pixels = imageData.data;
for (var i = 0, n = pixels.length; i < n; i += 4) {
//This will get the data/values for one pixel
var red = pixels[i];
var green = pixels [i+1];
var blue = pixels [i+2];
var alpha = pixels [i+3];
//This will invert the colours
pixels[i] = 255 - red;
pixels[i+1] = 255 - green;
pixels[i+2] = 255 - blue;
}
context.putImageData(imageData, 0, 0);
function checkForCollision() {
var imgData = context.getImageData(x-1, y-1, 15+2, 15+2);
var pixels = imgData.data;
//Then we need to perform a check, same as above
for (var i = 0; n = pixels.length, i < n; i += 4) {
var red = pixels[i];
var green = pixels[i+1];
var blue = pixels[i+2];
var alpha = pixels[i+3];
//now check for the black pixels for a wall
if (red == 0 && green == 0 && blue == 0) {
return true;
} //checks for a greyish colour - possibly the edge of a wall
if (red == 169 && green == 169 && blue == 169) {
return true;
}
}
return false; //there was no collision
}
</script>
</body>
</html>
There are lots of errors in this code. For example, in this section alone:
(commented where some issues are)
function drawFrame() {
if (dx != 0 || dy != 0) {
context.beginPath();
context.fillStyle = "rgb(254,244,207)";
context.rect(x, y, 15, 15);
context.fill // Missing parentheses and semicolon
x += dx;
y += dy;
if (checkForCollision()) {
(dx/y = 0) // Equivalent to { dx / (y = 0) }
x -= dx; // which both serves no purpose and divides by zero
y -= dy;
dx = 0;
dy = 0;
}
//Now we can finally draw the sprite!
var imgSprite = document.getElementById("sprite");
context.drawImage(imgSprite, x, y);
if (y > (canvas.height - 17)) {
alert("Congratulations! You made it!");
return;
}
}
timer = setTimeout(drawFrame, 10); // timer is not defined anywhere
// also you are calling the function within itself
// with no end condition, so it's an infinite loop
}
And on line 48:
setTimeout("drawFrame()", 10);
You should be passing the function as just the function identifier, not as a string. As such:
setTimeout(drawFrame, 10);
These are just a few. There are also some logical errors, variables that are defined but never used, and more. In its current state this will not compile.
Also, if only just for clarity's sake, try to define a script type instead of just leaving the tag empty, like:
<script type="text/javascript">
// Some JS
</script>
It can be hard when you've been staring at code for hours, but it helps to give each section a slow read-through and think about exactly what the code is doing. You can avoid lots of syntactical and logical errors this way.
I also recommend using a text editor or online interface (JS Bin, JSfiddle, etc.) that has JShint/lint or some kind of code-checking functionality. You can even use http://www.javascriptlint.com/online_lint.php and paste your whole code in there.

Canvas javascript event based on a object

I need to do an action onclick of a particular (point) or a rectangle in a canvas.
Example:
$(document).ready(function(){
var canvas = $('#myCanvas').get(0);
if (!canvas.getContext) { return; }
var ctx = canvas.getContext('2d');
ctx.fillRect(150,140,8,8);
ctx.fillRect(200,120,8,8);
ctx.fillRect(200,160,8,8);
});
I need to connect two points with a line and another two points with a curve using javascript .How can i do this?
You need to maintain the regions yourselves. There are no objects on a canvas, only pixels and the browser does not know anything about it.
Demo here
You can do something like this (simplified):
// define the regions - common for draw/redraw and check
var rect1 = [150,140,8,8];
var rect2 = [200,120,8,8];
var rect3 = [200,160,8,8];
var regions = [rect1, rect2, rect3];
Now on your init you can use the same array to render all the rectangles:
$(document).ready(function(){
var canvas = $('#myCanvas').get(0);
if (!canvas.getContext) { return; }
var ctx = canvas.getContext('2d');
//use the array also to render the boxes
for (var i = 0, r; r = regions[i]; i++) {
ctx.fillRect(r[0],r[1],r[2],r[3]);
}
});
On the click event you check the array to see if the mouse coordinate (corrected for canvas) is inside any of the rectangles:
$('#myCanvas').on('click', function(e){
var pos = getMousePos(this, e);
// check if we got a hit
for (var i = 0, r; r = regions[i]; i++) {
if (pos.x >= r[0] && pos.x <= (r[0] + r[2]) &&
pos.y >= r[1] && pos.y <= (r[1] + r[3])) {
alert('Region ' + i + ' was hit');
}
}
});
//get mouse position relative to canvas
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
Also remember to redraw the canvas if the window is re-sized or for other reason clears the canvas (browser dialogs etc.).
To connect the boxes you need to store the first hit position and when you get a second hit draw a line between them.
Demo with lines here
Add to the global vars and also make canvas and context available from global (see fiddle for related modifications in onready):
var x1 = -1, y1;
var canvas = myCanvas;
var ctx = canvas.getContext('2d');
And in the click event:
$('#myCanvas').on('click', function(e){
var pos = getMousePos(this, e);
for (var i = 0, r; r = regions[i]; i++) {
if (pos.x >= r[0] && pos.x <= (r[0] + r[2]) &&
pos.y >= r[1] && pos.y <= (r[1] + r[3])) {
//first hit? then store the coords
if (x1 === -1) {
x1 = pos.x;
y1 = pos.y;
} else {
//draw line from first point to this
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
x1 = -1; //reset (or keep if you want continuous lines).
};
}
}
});

Using midpoint circle algorithm to generate points of a filled circle

I need to generate and store the coordinates of each point of a filled circle of say, radius 10 in Javascript.
It seems like the best way to do this would be to use the midpoint circle algorithm, but I'm not sure how to adapt it to find every point in the circle. The coordinates are going to be stored as objects in an array.
Could someone help me with the implementation?
Personally I think it would probably be faster in this case to test all pixels in the bounding box for their distance to the center. If <= r then the point is in the circle and should be pushed onto your array.
function distance(p1, p2)
{
dx = p2.x - p1.x; dx *= dx;
dy = p2.y - p1.y; dy *= dy;
return Math.sqrt( dx + dy );
}
function getPoints(x, y, r)
{
var ret = [];
for (var j=x-r; j<=x+r; j++)
for (var k=y-r; k<=y+r; k++)
if (distance({x:j,y:k},{x:x,y:y}) <= r) ret.push({x:j,y:k});
return ret;
}
You loop through all the possible points and you run the Point-In-Circle check on them.
Something like the following would suffice...
var result = [];
var d = 10;
var r = d / 2;
var rr = r*r;
for(var y=0; y<d; y++)
for(var x=0; x<d; x++)
if((x-r)*(x-r)+(y-r)*(y-r) < rr)
result.push({"x": x, "y": y});
Modifying the above algorithm to handle other (more complex) shapes/path/polygons would be difficult. For a more generic solution you could use HTML5 CANVAS. You create a canvas, get the 2d context draw all of your shapes/paths/polygons in solid black then iterate through the pixel data and find the pixels with an alpha channel greater than 0 (or 127 if you want to alleviate false positives from anti-aliasing).
var r = 5; // radius of bounding circle
//
// set up a canvas element
//
var canvas = document.createElement("canvas");
canvas.width = r*2;
canvas.height = r*2;
canvas.style.width = (r*2) + "px";
canvas.style.height = (r*2) + "px";
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#000";
//
// draw your shapes/paths/polys here
//
ctx.beginPath();
ctx.arc(r, r, r, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
//
// process the pixel data
//
var imageData = ctx.getImageData(0,0,(r*2),(r*2));
var data = imageData.data;
var result = [];
var str = "";
for(var y = 0; y<(r*2); y++) {
for(var x = 0; x<(r*2); x++) {
var pixelOffset = (y * (r*2) + x) * 4;
if(data[pixelOffset+3] > 127) {
result.push({x: x, y: y});
str += "(" + x + ", " + y + ") "; // debug
}
}
}
//
// debug/test output
//
document.body.innerHTML += str;
document.body.appendChild(canvas);
alert(result.length);

Categories