Html5 canvas showing an array in a grapich does not turn - javascript

Here you can find the code of my little program.
http://jsfiddle.net/joz8h9hc/
It's aim is to find out how to turn a square that depicts the values of a 2d array, in order to allow visualization of 2d mathematical arrays.
Why it's not turning clockwise or counterclockwise?
var canvas=document.getElementById('canvas');
var ctx=canvas.getContext('2d');
//GIRADOR
ctx.translate(canvas.width / 2, canvas.height / 2);
//FIN GIRADOR
var array = new Array2D(200,200);
//MAIN
for(i=0; i<200; i++)
{
for(j=0;j<200; j++)
{
array[i][j]=i+j;
var r,g,b;
r = array[i][j];
g=50;
b=50;
//La parte de dibujo
ctx.fillStyle = "rgba("+r+","+g+","+b+",100)";
ctx.fillRect( i, j, 1, 1 );
}
}
//FUNCTIONS
function Array2D(NumOfRows,NumOfCols)
{
var k=new Array(NumOfRows);
for (i = 0; i < k.length; ++i)
k[i] = new Array(NumOfCols);
return k;
}
function Rotar(){
//Rotamos el lienzo?
ctx.rotate(Math.PI / 180);
ctx.fillStyle = "red";
ctx.fillRect( -200, -200, 600, 600 );
for(i=0; i<200; i++)
{
for(j=0;j<200; j++)
{
array[i][j]=i+j;
var r,g,b;
r = array[i][j];
g=50;
b=50;
//La parte de dibujo
ctx.fillStyle = "rgba("+r+","+g+","+b+",100)";
ctx.fillRect( i, j, 1, 1 );
}
}
}

context.translate will set your rotation point. All subsequent drawings will rotate around this point.
Therefore, if you're subsequently drawing a rectangle, the rectangle will be drawn with its top-left corner at your translate point. The rect will rotate around its own top-left corner.
To rotate a rect around its centerpoint, you would set the x,y of fillRect to -width/2, -height/2.
Also, transformations (translate, rotate) are cumulative so you probably want to wrap your transformations in context.save and context.restore so subsequent transformations don't interact with previous transformations.
Here's example code and a demo illustrating rotating a rectangle around its centerpoint:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// variables to define the rectangle
var x=100;
var y=100;
var width=100;
var height=75;
var radianAngle=0;
// draw the rectangle
rotateSquare();
function rotateSquare(){
// clear the canvas
ctx.clearRect(0,0,canvas.width,canvas.height);
// save the unrotated context state
ctx.save();
// set the rotation point at the rectangle's centerpoint
ctx.translate(x+width/2,y+height/2);
// set the rotation angle
ctx.rotate(radianAngle);
// draw the rectangle
ctx.fillRect(-width/2,-height/2,width,height);
ctx.strokeRect(-width/2,-height/2,width,height);
// restore the context to its unrotated state
ctx.restore();
}
$("#test").click(function(){
// reset the angle
radianAngle+=Math.PI/60;
// call rotateSquare
rotateSquare();
});
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id="test">Rotate</button><br>
<canvas id="canvas" width=300 height=300></canvas>

Related

Rotate individual objects in canvas?

the rotate() function seems to rotate the whole drawing area. Is there a way to rotate paths individually? I want the center for the rotation to be the object, not the drawing area.
Using save() and restore() still makes rotate take into account the whole drawing area.
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.save();
context.fillStyle = 'red';
context.rotate(0.35);
context.fillRect(40,40, 100, 100);
context.restore();
context.save();
context.fillStyle = 'blue';
context.rotate(0.35);
context.fillRect(200, 40, 100, 100);
context.restore();
<canvas id="canvas" width="500" height="500"></canvas>
Use local space
Instead of drawing object at the position you want them draw everything around its own origin in its local space. The origin is at (0,0) and is the location that the object rotates around.
So if you have a rectangle that you draw with
function drawRect(){
context.fillRect(200, 40, 100, 100);
}
change it so that it is drawn at its origin
function drawRect(){
context.fillRect(-50,-50 , 100, 100);
}
Now you can easily draw it wherevery you want
Start with the setTransform function as that clears any existing tranforms and is a convenient way to set the location of the center of the object will be
ctx.setTransform(1,0,0,1,posX,posY); // clear transform and set center location
if you want to rotate it then add the rotation
ctx.rotate(ang);
and scale with
ctx.scale(scale,scale);
if you have two different scales you should scale before the rotate.
Now just call the draw function
drawRect();
and it is drawn with its center at posX,posY rotated and scaled.
You can combine it all into a function that has the x,y position, the width and the height, scale and rotation. You can include the scale in the setTransform
function drawRect(x,y,w,h,scale,rotation){
ctx.setTransform(scale,0,0,scale,x,y);
ctx.rotate(rotation);
ctx.strokeRect(-w/2,-h/2,w,h);
}
It also applies to an image as a sprite, and I will include a alpha
function drawImage(img,x,y,w,h,scale,rotation,alpha){
ctx.globalAlpha = alpha;
ctx.setTransform(scale,0,0,scale,x,y);
ctx.rotate(rotation);
ctx.drawImage(img,-img.width/2,-img.height/2,img.width,img.height);
}
On a 6 year old laptop that can draw 2000 sprites on firefox every 1/60th of a second, each rotated, scaled, positioned, and with a alpha fade.
No need to mess about with translating back and forward. Just keep all the objects you draw around there own origins and move that origin via the transform.
Update
Lost the demo so here it is to show how to do it in practice.
Just draws a lot of rotated, scaled translated, alphaed rectangles.
By using setTransform you save a lot of time by avoiding save and restore
// create canvas and add resize
var canvas,ctx;
function createCanvas(){
canvas = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.left = "0px";
canvas.style.top = "0px";
canvas.style.zIndex = 1000;
document.body.appendChild(canvas);
}
function resizeCanvas(){
if(canvas === undefined){
createCanvas();
}
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
}
resizeCanvas();
window.addEventListener("resize",resizeCanvas);
// simple function to draw a rectangle
var drawRect = function(x,y,w,h,scale,rot,alpha,col){
ctx.setTransform(scale,0,0,scale,x,y);
ctx.rotate(rot);
ctx.globalAlpha = alpha;
ctx.strokeStyle = col;
ctx.strokeRect(-w/2,-h/2, w, h);
}
// create some rectangles in unit scale so that they can be scaled to fit
// what ever screen size this is in
var rects = [];
for(var i = 0; i < 200; i ++){
rects[i] = {
x : Math.random(),
y : Math.random(),
w : Math.random() * 0.1,
h : Math.random() * 0.1,
scale : 1,
rotate : 0,
dr : (Math.random() - 0.5)*0.1, // rotation rate
ds : Math.random()*0.01, // scale vary rate
da : Math.random()*0.01, // alpha vary rate
col : "hsl("+Math.floor(Math.random()*360)+",100%,50%)",
};
}
// draw everything once a frame
function update(time){
var w,h;
w = canvas.width; // get canvas size incase there has been a resize
h = canvas.height;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.clearRect(0,0,w,h); // clear the canvas
// update and draw each rect
for(var i = 0; i < rects.length; i ++){
var rec = rects[i];
rec.rotate += rec.dr;
drawRect(rec.x * w, rec.y * h, rec.w * w,rec.h * h,rec.scale + Math.sin(time * rec.ds) * 0.4,rec.rotate,Math.sin(time * rec.da) *0.5 + 0.5,rec.col);
}
requestAnimationFrame(update); // do it all again
}
requestAnimationFrame(update);
All transformations in canvas are for the whole drawing area. If you want to rotate around a point you're going to have to translate that point to the origin, do your rotation and translate it back. Something like this is what you want.
Use a rotate function to rotate all of the shape's points around its center.
<!DOCTYPE html>
<html>
<head>
<style>
body
{
margin: 0px;
padding: 0px;
overflow: hidden;
}
canvas
{
position: absolute;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
var canvas;
var context;
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var degreesToRadians = function(degrees)
{
return degrees*Math.PI/180;
}
var rotate = function(x, y, cx, cy, degrees)
{
var radians = degreesToRadians(degrees);
var cos = Math.cos(radians);
var sin = Math.sin(radians);
var nx = (cos * (x - cx)) + (sin * (y - cy)) + cx;
var ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
return new Vector2(nx, ny);
}
var Vector2 = function(x, y)
{
return {x:x,y:y};
}
var Shape = function(points, color)
{
this.color = color;
this.points = points;
};
Shape.prototype.rotate = function(degrees)
{
var center = this.getCenter();
for (var i = 0; i < this.points.length; i++)
{
this.points[i] = rotate(this.points[i].x,this.points[i].y,center.x,center.y,degrees);
}
context.beginPath();
context.arc(center.x,center.y,35,0,Math.PI*2);
context.closePath();
context.stroke();
}
Shape.prototype.draw = function()
{
context.fillStyle = this.color;
context.strokeStyle = "#000000";
context.beginPath();
context.moveTo(this.points[0].x, this.points[0].y);
for (var i = 0; i < this.points.length; i++)
{
context.lineTo(this.points[i].x, this.points[i].y);
//context.fillText(i+1, this.points[i].x, this.points[i].y);
}
context.closePath();
context.fill();
context.stroke();
}
Shape.prototype.getCenter = function()
{
var center = {x:0,y:0};
for (var i = 0; i < this.points.length; i++)
{
center.x += this.points[i].x;
center.y += this.points[i].y;
}
center.x /= this.points.length;
center.y /= this.points.length;
return center;
}
Shape.prototype.translate = function(x, y)
{
for (var i = 0; i < this.points.length; i++)
{
this.points[i].x += x;
this.points[i].y += y;
}
}
var Rect = function(x,y,w,h,c)
{
this.color = c;
this.points = [Vector2(x,y),Vector2(x+w,y),Vector2(x+w,y+h),Vector2(x,y+h)];
}
Rect.prototype = Shape.prototype;
var r = new Rect(50, 50, 200, 100, "#ff0000");
r.draw();
r.translate(300,0);
r.rotate(30);
r.draw();
</script>
</body>
</html>

How to drag shapes (like rect,circle,other polygons,etc) around the canvas using native javascript

Am simulating a shape click and trying to do drag/drop inside canvas using Sahi framework.I have found some references that by looping through an array containing that shapes position/sizes , we get whether shape is clicked.While testing other website using sahi framework/native jaascript , how can it be achieved ? I wanted to select that shape inside canvas and do drag and drop inside canvas.This is my goal.
In html5 canvas, A "Shape" is defined and drawn using a Path.
You define a shape by:
Declaring that you a beginning a new path: context.beginPath
Using one or more of the path definition commands: moveTo, lineTo, arc, etc.
// define a triangle path
context.beginPath();
context.moveTo(x+50,y+50);
context.lineTo(x+100,y+100);
context.lineTo(x+0,y+100);
// see not below about .closePath()
context.closePath();
Note: context.closePath is NOT used to close a context.beginPath command -- it is NOT like a closing parenthesis! Instead, it is used to draw a line from the last coordinate to the starting coordinate -- to "enclose" the path. In our example, it draws an "enclosing" line from [x+0,y+100] to [x+50,y+50].
Just defining a path will not cause it to be drawn onto the canvas. To actually draw the shape onto the canvas you can:
Stroke the outline of the path, and / or
Fill the inside of the path.
context.stroke();
context.fill();
For example, here's how to define and draw a triangle. You can also use offset variables ([x,y] in the example) to reposition the triangle anywhere on the canvas.
// set the offset of the triangle
var x=30;
var y=40;
// define the path
context.beginPath();
context.moveTo(x+50,y+50);
context.lineTo(x+100,y+100);
context.lineTo(x+0,y+100);
context.closePath();
// stroke the path
context.stroke();
// if desired, you can also fill the inside of the path
context.fill();
To drag a shape, you must test whether the mouse is over that shape. You can "hit-test" the most recently defined shape using context.isPointInPath.
Be sure you read that carefully!
You can hit-test the most recently "defined" path. If you define and draw multiple paths then isPointInPath will only hit-test the last defined path.
if(context.isPointInPath(mouseX,mouseY)){
console.log('Yes, the mouse is in the triangle.');
}
Also note that you don't have to re-stroke the path being tested so your drawings won't be altered by the hit-testing process. So you hit-test multiple paths by:
Define one of the paths
Hit test with isPointInPath(mouseX,mouseY)
Repeat step#1 for the next path (path==shape)
Nothing drawn to the canvas can be moved -- everything is as permanent as dried paint. So to "move" a shape on canvas you clear the entire canvas and redraw the shape in its moved position:
// clear the canvas
context.clearRect(canvas.width,canvas.height);
// move the canvas by changing it's offsets
x+=20;
y+=30;
// redefine and restroke the shape
context.beginPath();
context.moveTo(x+50,y+50);
context.lineTo(x+100,y+100);
context.lineTo(x+0,y+100);
context.closePath();
context.stroke();
To make redefining and re-stroking the shape more reusable you can put the code in a function:
function myTriangle(alsoStroke){
context.beginPath();
context.moveTo(x+50,y+50);
context.lineTo(x+100,y+100);
context.lineTo(x+0,y+100);
context.closePath();
if(alsoStroke){
context.stroke();
}
}
You can read more about dragging a shape in this previous post. Since you can't move a shape you can't "drag" a shape either. You must again clear the canvas and redraw it in its newly dragged position.
To drag a shape you need to listen to 4 mouse events.
In mousedown: Check if the mouse is over the shape, and, if yes, set a flag indicating a drag has begun. To check if the mouse is over the shape, you can use canvas context's isPointInPath method which tests if an [x,y] point is inside the most recently drawn path.
In mousemove: If the dragging flag is set (indicating a drag is in process), change the position of the selected text by the distance the user has dragged and redraw the shape in its new position
In mouseup or in mouseout: The drag is over so clear the dragging flag.
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
var isDown=false;
var startX,startY;
var poly={
x:0,
y:0,
points:[{x:50,y:50},{x:75,y:25},{x:100,y:50},{x:75,y:125},{x:50,y:50}],
}
ctx.fillStyle='skyblue';
ctx.strokeStyle='gray';
ctx.lineWidth=3;
draw();
// listen to mouse events
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
$("#canvas").mouseout(function(e){handleMouseOut(e);});
function draw(){
ctx.clearRect(0,0,cw,ch);
define();
ctx.fill();
ctx.stroke()
}
function define(){
ctx.beginPath();
ctx.moveTo(poly.points[0].x+poly.x,poly.points[0].y+poly.y);
for(var i=0;i<poly.points.length;i++){
ctx.lineTo(poly.points[i].x+poly.x,poly.points[i].y+poly.y);
}
ctx.closePath();
}
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
// Put your mousedown stuff here
define();
if(ctx.isPointInPath(startX,startY)){
isDown=true;
}
}
function handleMouseUp(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mouseup stuff here
isDown=false;
}
function handleMouseOut(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mouseOut stuff here
isDown=false;
}
function handleMouseMove(e){
if(!isDown){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousemove stuff here
var dx=mouseX-startX;
var dy=mouseY-startY;
startX=mouseX;
startY=mouseY;
poly.x+=dx;
poly.y+=dy;
draw();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Drag the polygon</h4>
<canvas id="canvas" width=300 height=300></canvas>
[ Addition: Discovering Rectangles using context.getImageData ]
If you don't have the shape's position/size and you instead have an image of a shape then you must get the position/size by searching the pixels. Here's an example showing how to isolate rectangles by searching pixels:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cw, ch;
// background definition
// OPTION: look at the top-left pixel and assume == background
// then set these vars automatically
var isTransparent = false;
var bkColor = {
r: 255,
g: 255,
b: 255
};
var bkFillColor = "rgb(" + bkColor.r + "," + bkColor.g + "," + bkColor.b + ")";
cw = canvas.width;
ch = canvas.height;
ctx.fillStyle = "white";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawTestRect(30, 30, 50, 50, "1");
drawTestRect(100, 30, 50, 30, "2");
drawTestRect(170, 30, 30, 50, "3");
function drawTestRect(x, y, w, h, label) {
ctx.fillStyle = "black";
ctx.fillRect(x, y, w, h);
ctx.fillStyle = "white";
ctx.font = "24px verdana";
ctx.fillText(label, x + 10, y + 25);
}
function clipBox(data) {
var pos = findEdge(data);
if (!pos.valid) {
return;
}
var bb = findBoundary(pos, data);
alert("Found target at "+bb.x+"/"+bb.y+", size: "+bb.width+"/"+bb.height);
clipToImage(bb.x, bb.y, bb.width, bb.height);
if (isTransparent) {
// clear the clipped area
// plus a few pixels to clear any anti-aliasing
ctx.clearRect(bb.x - 2, bb.y - 2, bb.width + 4, bb.height + 4);
} else {
// fill the clipped area with the bkColor
// plus a few pixels to clear any anti-aliasing
ctx.fillStyle = bkFillColor;
ctx.fillRect(bb.x - 2, bb.y - 2, bb.width + 4, bb.height + 4);
}
}
function xyIsInImage(data, x, y) {
// find the starting index of the r,g,b,a of pixel x,y
var start = (y * cw + x) * 4;
if (isTransparent) {
return (data[start + 3] > 25);
} else {
var r = data[start + 0];
var g = data[start + 1];
var b = data[start + 2];
var a = data[start + 3]; // pixel alpha (opacity)
var deltaR = Math.abs(bkColor.r - r);
var deltaG = Math.abs(bkColor.g - g);
var deltaB = Math.abs(bkColor.b - b);
return (!(deltaR < 5 && deltaG < 5 && deltaB < 5 && a > 25));
}
}
function findEdge(data) {
for (var y = 0; y < ch; y++) {
for (var x = 0; x < cw; x++) {
if (xyIsInImage(data, x, y)) {
return ({
x: x,
y: y,
valid: true
});
}
}
}
return ({
x: -100,
y: -100,
valid: false
});
}
function findBoundary(pos, data) {
var x0 = x1 = pos.x;
var y0 = y1 = pos.y;
while (y1 <= ch && xyIsInImage(data, x1, y1)) {
y1++;
}
var x2 = x1;
var y2 = y1 - 1;
while (x2 <= cw && xyIsInImage(data, x2, y2)) {
x2++;
}
return ({
x: x0,
y: y0,
width: x2 - x0,
height: y2 - y0 + 1
});
}
function drawLine(x1, y1, x2, y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.strokeStyle = "red";
ctx.lineWidth = 0.50;
ctx.stroke();
}
function clipToImage(x, y, w, h) {
// don't save anti-alias slivers
if (w < 3 || h < 3) {
return;
}
// save clipped area to an img element
var tempCanvas = document.createElement("canvas");
var tempCtx = tempCanvas.getContext("2d");
tempCanvas.width = w;
tempCanvas.height = h;
tempCtx.drawImage(canvas, x, y, w, h, 0, 0, w, h);
var image = new Image();
image.width = w;
image.height = h;
image.src = tempCanvas.toDataURL();
$("#clips").append(image);
}
$("#unbox").click(function () {
var imgData = ctx.getImageData(0, 0, cw, ch);
var data = imgData.data;
clipBox(data);
});
body {
background-color: ivory;
}
canvas {
border:1px solid red;
}
#clips {
border:1px solid blue;
padding:5px;
}
img {
margin:3px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id="unbox">Clip next sub-image</button><br>
<canvas id="canvas" width=300 height=150></canvas><br>
<h4>Below are images clipped from the canvas above.</h4><br>
<div id="clips"></div>
[Addition: discovering the bounds of a reddishness stroked rectangle]
You can test for "reddishness" by checking if the red component value of a pixel is much larger than the green & blue component values.
function xyIsInImage(data, x, y) {
// find the starting index of the r,g,b,a of pixel x,y
var n = (y * cw + x) * 4;
return(data[n+3]>240 && // this pixel is mostly opaque
data[n]-data[n+1]>180 && // this pixel is more reddish than green
data[n]-data[n+2]>180 // this pixel is more reddish then blue
);
Then use this reddishness test to find the bounds of the reddish stroked rectangle:
Find the theoretical top-left pixel of the red rect. The discovered pixel is probably on the top of the rect. It might not be on the left of the rect because your image shows the rect's corner pixels are much less reddish. So declare the y value as the top of the rect.
Move towards the center of the rect by a few pixels.
Test each vertical pixel downward until you find the bottom reddish border of the stroked rect.
Test each horizontal pixel rightward until you find the right reddish border of the stroked rect.
Test each horizontal pixel leftward until you find the left reddish border of the stroked rect.
Now you have found the top-left and bottom-right bounds of the rect.
Here's example code and a demo using your image:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var img=new Image();
img.crossOrigin='anonymous';
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/raw.png";
function start(){
cw=canvas.width=img.width;
ch=canvas.height=img.height;
ctx.drawImage(img,0,0);
var data=ctx.getImageData(0,0,cw,ch).data;
var edge=findEdge(data);
var top=edge.y;
var x,y,left,bottom,right;
var off=25;
for(var y=edge.y+off;y<ch;y++){if(xyIsInImage(data,edge.x+off,y)){bottom=y; break;}}
for(var x=edge.x+off;x<cw;x++){if(xyIsInImage(data,x,edge.y+off)){right=x;break;}}
for(var x=edge.x+off;x>=0;x--){if(xyIsInImage(data,x,edge.y+off)){left=x;break;}}
dot({x:left,y:top});
dot({x:right,y:bottom});
}
//
function dot(pt){
ctx.beginPath();
ctx.arc(pt.x,pt.y,4,0,Math.PI*2);
ctx.closePath();
ctx.fillStyle='red';
ctx.fill();
ctx.strokeStyle='gold';
ctx.lineWidth=2;
ctx.stroke();
}
function xyIsInImage(data, x, y) {
// find the starting index of the r,g,b,a of pixel x,y
var n = (y * cw + x) * 4;
return(data[n+3]>240 &&
data[n]-data[n+1]>180 &&
data[n]-data[n+2]>180
);
}
function findEdge(data) {
for (var y = 0; y < ch; y++) {
for (var x = 0; x < cw; x++) {
if(xyIsInImage(data, x, y)){ return ({x:x,y:y,valid:true}); }
}
}
return ({x:-100,y:-100,valid:false});
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<h4>Red-gold dots are on top-left & bottom-right of target rect.</h4>
<canvas id="canvas" width=300 height=300></canvas>

Canvas place image in shape [duplicate]

This question already has an answer here:
How to fillstyle with Images in canvas html5
(1 answer)
Closed 7 years ago.
I'm trying to create a spinning wheel of sorts, where an image is displayed as a prize. I'm reusing a project I found online, and I'm pretty new to canvas, so I would appreciate some help.
This is how it looks, here an image would be displayed in each of the fields, with as angle to match the wheel. Here is the code generating it:
var outsideRadius = 210;
var textRadius = 160;
var insideRadius = 155;
ctx = canvas.getContext("2d");
ctx.clearRect(0,0,500,500);
ctx.strokeStyle = "#943127";
ctx.lineWidth = 4;
for(var i = 0; i < 12; i++) {
var angle = startAngle + i * arc;
ctx.fillStyle = '#a9382d';
ctx.beginPath();
ctx.arc(250, 250, outsideRadius, angle, angle + arc, false);
ctx.arc(250, 250, insideRadius, angle + arc, angle, true);
ctx.closePath();
ctx.stroke();
ctx.fill();
}
In each of the fields above should be displayed a image of a prize from an array. Im having problems drawing the images in the fields. I've tried using createPattern() without luck.
EDIT: Added jsfiddle: http://jsfiddle.net/46k72m7z/
To clip an image inside one of your wheel-wedges:
See illustration below.
Calculate the 4 vertices of your specified wedge.
Begin a new Path with context.beginPath and move to point0.
Draw a line from point0 to point1.
Draw an arc from point1 to point2.
Draw a line from point2 to point3.
Draw an arc from point3 back to point0.
Close the path (not needed, just being extra careful).
Call context.clip() to create a clipping area of your wedge.
Drawing the image at the appropriate angle
See illustration below.
Find the centerpoint of the wedge.
Set the canvas origin to that centerpoint with context.translate.
Calculate the angle from the wheel center (point#1 below) to the wedge center.
Rotate the canvas by the calculated angle.
Draw the image offset by half the image's width & height. This causes the image to be visually centered inside the wedge.
Other useful information
When you set a clip with context.clip the only way to undo that clip is to wrap the clip between context.save and context.restore. (Or resize the canvas, but that's counter-productive when you're trying to draw multiple clipped regions because all content is erased when the canvas is resized).
// save the begininning unclipped context state
context.save();
... draw your path
// create the clipping area
context.clip();
... draw the image inside the clipping area
// restore the context to its unclipped state
context.restore();
To center an image anywhere, find the center point where you want the image centered and then draw the image offset by half it's width & height:
ctx.drawImage( img,-img.width/2, -img.height/2 );
Calculating points on the circumference of a circle:
// given...
var centerX=150; // the circle's center
var centerY=150;
var radius=25; // the circle's radius
var angle=PI/2; // the desired angle on the circle (angles are expressed in radians)
var x = centerX + radius * Math.cos(angle);
var y = centerY + radius * Math.sin(angle);
An efficiency: The path command will automatically draw a line from the previous command's endpoint to your new command's startpoint. Therefore, you can skip step#3 & step#5 because the lines will be drawn automatically when you draw the arcs.
Example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
var PI=Math.PI;
var cx=250;
var cy=250;
var outsideRadius = 210;
var textRadius = 160;
var insideRadius = 155;
var wedgecount=12;
var arc=Math.PI*2/wedgecount;
var startAngle=-arc/2-PI/2;
var mm=new Image;
mm.onload=start;
mm.src='https://dl.dropboxusercontent.com/u/139992952/multple/mm1.jpg';
var goldCar=new Image();
goldCar.onload=start;
goldCar.src='https://dl.dropboxusercontent.com/u/139992952/multple/carGold.png';
var redCar=new Image();
redCar.onload=start;
redCar.src='https://dl.dropboxusercontent.com/u/139992952/multple/cars1.png';
var imgCount=2;
function start(){
if(++imgCount<2){return;}
drawWheel();
for(var i=0;i<wedgecount;i++){
var img=(i%2==0)?goldCar:redCar;
if(i==3){img=mm;}
clipImageToWedge(i,img);
}
}
function drawWheel(){
ctx.clearRect(0,0,500,500);
ctx.lineWidth = 4;
for(var i = 0; i < 12; i++) {
var angle = startAngle + i * arc;
ctx.fillStyle = '#a9382d';
ctx.beginPath();
ctx.arc(cx,cy, outsideRadius, angle, angle + arc, false);
ctx.arc(cx,cy, insideRadius, angle + arc, angle, true);
ctx.closePath();
ctx.stroke();
ctx.fill();
}
}
function clipImageToWedge(index,img){
var angle = startAngle+arc*index;
var angle1= startAngle+arc*(index+1);
var x0=cx+insideRadius*Math.cos(angle);
var y0=cy+insideRadius*Math.sin(angle);
var x1=cx+outsideRadius*Math.cos(angle);
var y1=cy+outsideRadius*Math.sin(angle);
var x3=cx+outsideRadius*Math.cos(angle1);
var y3=cy+outsideRadius*Math.sin(angle1);
ctx.save();
ctx.beginPath();
ctx.moveTo(x0,y0);
ctx.lineTo(x1,y1);
ctx.arc(cx,cy,outsideRadius,angle,angle1);
ctx.arc(cx,cy,insideRadius,angle1,angle,true);
ctx.clip();
var midRadius=(insideRadius+outsideRadius)/2;
var midAngle=(angle+angle1)/2;
var midX=cx+midRadius*Math.cos(midAngle);
var midY=cy+midRadius*Math.sin(midAngle);
ctx.translate(midX,midY);
ctx.rotate(midAngle+PI/2);
ctx.drawImage(img,-img.width/2,-img.height/2);
ctx.restore();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=500 height=500></canvas>

Drag Move an Image inside a Circle (circular movement)

I drawn a circle in canvas and put an image near the border. Now I have absolutely no idea..I want to drag the image around the circle but the top of the arrow image should always be on the border.
For Example: I drag the arrow from the top to the left at nine o'clock. Now the arrow image needs to be rotated 90 degrees.
http://jsfiddle.net/L5twk3ak/1/
canvas = document.getElementById('test');
var context = canvas.getContext("2d");
var points = [];
var radius = 55;
imageBG = new Image();
imageBG.onload = function() {context.drawImage(imageBG, 148, 100, 15, 15);};
imageBG.src = 'https://www.nanamee.com/upload/images/5945/5945_p.jpg';
for(var degree = 0; degree < 360; degree++)
{
var radians = degree * Math.PI / 179;
var x = 150 + radius * Math.cos(radians);
var y = 150 + radius * Math.sin(radians);
points.push({x : x, y : y});
}
context.beginPath();
context.moveTo(points[0].x + 4, points[0].y + 4)
for(var i = 1; i < points.length; i++)
{
var pt = points[i];
context.lineTo(pt.x + 4, pt.y + 4);
}
context.strokeStyle = "black";
context.lineWidth = 1;
context.stroke();
context.closePath();
<canvas id="test" width="400" height="400">Your browser does not support the HTML5 canvas tag.</canvas>
You need to :
Draw your Arc as we're supposed to (unless you have better plans with lineTo() )
calculate the mouse position inside the canvas - on mousemove.
calculate the resultant degree depending on Mouse Position vs. the Arc center.
cache your image for reuse
create draw functions (one for the Arc, the other for drawing the Image after translating the canvas context). That way on (click+drag) mousemove you can simply reuse them to draw your objects into Canvas.
I'll not show you how to implement the click+drag cause it's pretty trivial: you simply need to apply your draw functions if both CLICK+MOUSEMOVE are registered.
Here's the interesting calculations part:
var canvas = document.getElementById('test'); // Store in variable!
var context = canvas.getContext("2d");
var circle = {rad: 55, x:100, y:100}; // Object for ease of use
var img = {src:'//placehold.it/13x13/000', x:0 ,y:0, w:13, h:13};
var arrowImg; // Store for later Image reference
function drawArc(){
context.beginPath();
context.arc(circle.x, circle.y, circle.rad, 0, Math.PI*2, true);
context.strokeStyle = "#000";
context.lineWidth = 1;
context.stroke();
context.closePath();
}
function drawImg( deg ){
context.save(); // save before we mess with ctx translations
context.translate(circle.y, circle.x); // temporarily translate the ctx
// to the Arc center coordinates.
context.rotate(deg*Math.PI/180); // we need Radians so deg*Math.PI/180
context.drawImage(arrowImg, circle.rad-img.w, -img.h/2);
context.restore(); // restore to default
}
function calcDeg(e){ // Retrieve degree from mouse position vs. arc center
var mPos = {
x : e.pageX-canvas.offsetLeft-circle.x,
y : e.pageY-canvas.offsetTop-circle.y
};
var getAtan = Math.atan2(mPos.y, mPos.x);
return getAtan*180/Math.PI;
}
drawArc(); // Draw the ARc
arrowImg = new Image(); // Create Image Obj
arrowImg.onload = function(){ drawImg(-90) }; // onload draw the Image
arrowImg.src = img.src;
canvas.addEventListener("mousemove", function(evt){
canvas.width = canvas.width; // clear the canvas
drawArc(); // Draw Arc
drawImg( calcDeg(evt) ); // Draw Image at the calculated degree
}, false);
canvas{background:#eee;}
<canvas id="test" width="400" height="400">Your browser does not support the HTML5 canvas tag.</canvas>
Not clear? Goog, than ask

select and change color of a line in html5 canvas?

I wrote this code to draw random graphs. I have been trying in vain to find how I can select a line in the graph so that I can apply the prim's algorithm as one selects lines and see if they have found the minimum tree.
function draw(n,rep){
var cvs=document.getElementsByTagName('canvas')[0];
/**
* #type CanvasRenderingContext2D
**/
var ctx=cvs.getContext('2d');
ctx.beginPath();
var randomX=[];
var randomY=[];
ctx.lineWidth=2;
ctx.font = '3'+' Arial';
var weights=[];
var lastRandomx=Math.random()*200;
var lastRandomy=Math.random()*200;
for (var i = 0; i <n ; i++) {
var cwidth = cvs.width;
var cheight = cvs.height;
randomX[i] = Math.random()*cwidth*2/3;
randomY[i] = Math.random()*cheight*2/3;
weights[i]=Math.round(Math.random()*20);
ctx.fillRect(randomX[i],randomY[i],5,5);
ctx.moveTo(lastRandomx,lastRandomy);
ctx.lineTo(randomX[i],randomY[i]);
lastRandomx=randomX[i];
lastRandomy=randomY[i];
}
for (var i = 0; i < rep; i++) {
var rand=Math.round(rep*Math.random());
ctx.lineTo(randomX[rand],randomY[rand]);
}
ctx.closePath();
ctx.stroke();
};
I found this in stackoverflow and it doesn't help much. How to select lines that are drawn on a HTML5 Canvas?. I was wondering if there's a prewritten code so that I need not write it from scratch.
I was thinking if I could find the location of the mouse as it moves and each time check to see if the location of mouse is on the line as in here Finding if a point is on a line. Please help and suggest if there's any prewritten code because I'm restricted by time.
Thank you in advance.
You have to loop through your line array(s) and for each line segment do:
Core principle is to add a line to the path and then test if (x,y) is on that line:
ctx.beginPath();
ctx.moveTo(x1, y1); // start of line
ctx.lineTo(x2, y2); // end of line
// this will test the point against the line (lineWidth matters)
if (ctx.isPointInStroke(x, y)) {
// draw line segment in f.ex. different color here
ctx.strokeStyle = "red";
ctx.stroke(); // we already have a line segment on the path
}
There is no need to actually stroke the line, just rebuild the path. Adopt as needed.
Here is a full example:
var ctx = canvas.getContext("2d"),
lines = [], // store line segments for demo
count = 10, // max 10 lines for demo
i = 0;
for(; i < count; i++) {
var x = Math.random() * canvas.width; // random point for end points
var y = Math.random() * canvas.height;
if (i) ctx.lineTo(x, y); // if not first line, add lineTo
else ctx.moveTo(x, y); // start point
lines.push({ // store point to create a poly-line
x: x,
y: y
});
}
ctx.lineWidth = 5;
ctx.lineJoin = "round";
ctx.strokeStyle = "blue";
ctx.stroke(); // ..and draw line
// here we use the principle
canvas.onclick = function(e) {
var r = canvas.getBoundingClientRect(), // adjust to proper mouse position
x = e.clientX - r.left,
y = e.clientY - r.top,
i = 0
// for each line segment, build segment to path and check
for(; i < count - 1; i++) {
ctx.beginPath(); // new segment
ctx.moveTo(lines[i].x, lines[i].y); // start is current point
ctx.lineTo(lines[i+1].x, lines[i+1].y); // end point is next
if (ctx.isPointInStroke(x, y)) { // x,y is on line?
ctx.strokeStyle = "red"; // stroke red for demo
ctx.stroke();
break;
}
}
}
<canvas id=canvas width=500 height=500></canvas>
To increase sensitivity you can adjust lineWidth to a larger value (there is no need to redraw).
You can use math to determine which line is closest to the mouse.
Here's example code and a demo:
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var $canvas=$("#canvas");
var canvasOffset=$canvas.offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
ctx.lineWidth=2;
// linear interpolation -- needed in setClosestLine()
var lerp=function(a,b,x){ return(a+x*(b-a)); };
// vars to track which line is closest to the mouse
var closestLineIndex=-1;
var closestX,closestY;
// make some random lines and save them in lines[]
var n=5;
var lines=[];
var randomX=function(){return(Math.random()*cw*.67);}
var randomY=function(){return(Math.random()*ch*.67);}
var lastX=randomX();
var lastY=randomY();
for(var i=0;i<n;i++){
var x=Math.random()*cw*.67;
var y=Math.random()*ch*.67;
var dx=x-lastX;
var dy=y-lastY;
var line={
x0:lastX,
y0:lastY,
x1:x,
y1:y,
weight:Math.round(Math.random()*20),
// precalc often used values
dx:dx,
dy:dy,
dx2dy2:dx*dx+dy*dy,
};
lines.push(line);
lastX=x;
lastY=y;
}
redraw();
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
//////////////////////////////
function setClosestLine(mx,my) {
closestLineIndex=-1;
var minDistanceSquared=100000000;
// examine each line &
// determine which line is closest to the mouse (mx,my)
for(var i=0;i<lines.length;i++){
var line=lines[i];
var dx=line.x1-line.x0;
var dy=line.y1-line.y0;
var t=((mx-line.x0)*line.dx+(my-line.y0)*line.dy)/line.dx2dy2;
var x=lerp(line.x0, line.x1, t);
var y=lerp(line.y0, line.y1, t);
var dx1=mx-x;
var dy1=my-y;
var distSquared=dx1*dx1+dy1*dy1;
if(distSquared<minDistanceSquared){
minDistanceSquared=distSquared;
closestLineIndex=i;
closestX=x;
closestY=y;
}
}
};
function redraw(){
// clear the canvas
ctx.clearRect(0,0,cw,ch);
// draw all lines
ctx.strokeStyle='black';
for(var i=0;i<lines.length;i++){
var line=lines[i];
ctx.beginPath();
ctx.moveTo(line.x0,line.y0);
ctx.lineTo(line.x1,line.y1);
ctx.stroke();
}
// draw the line closest to the mouse in red
if(closestLineIndex<0){return;}
var line=lines[closestLineIndex];
ctx.strokeStyle='red';
ctx.beginPath();
ctx.moveTo(line.x0,line.y0);
ctx.lineTo(line.x1,line.y1);
ctx.stroke();
ctx.fillText("Index:"+closestLineIndex+", weight:"+line.weight,10,15);
}
function handleMouseMove(e){
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
setClosestLine(mouseX,mouseY);
redraw();
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Closest line is drawn in red<br>Closest line's weight is reported top-left</h4>
<canvas id="canvas" width=300 height=300></canvas>

Categories