Add Multiple Control Points in Canvas Shape - javascript

I have made canvas Shapes(squares) in kinetic js having 4 control points on each of their vertices.A user can click and drag these control points and distort the shape any way he/she pleases.The fiddle link is
http://jsfiddle.net/Lucy1/opsy1pn9/2/
The corresponding js code is
var room = new Kinetic.Shape({
x: 0,
y: 0,
width: w,
height: h,
stroke: "blue",
fill: 'red',
drawFunc: function (context) {
var x = this.x();
var y = this.y();
var w = this.width();
var h = this.height();
var tlX = this.anchorTL.x();
var tlY = this.anchorTL.y();
context.beginPath();
context.moveTo(tlX, tlY);
// top
context.bezierCurveTo(x + w / 3, y, x + w * 2 / 3, y, this.anchorTR.x(), this.anchorTR.y());
// right
context.bezierCurveTo(x + w, y + h / 3, x + w, y + h * 2 / 3, this.anchorBR.x(), this.anchorBR.y());
// bottom
context.bezierCurveTo(x + w * 2 / 3, y + h, x + w / 3, y + h, this.anchorBL.x(), this.anchorBL.y());
// left
context.bezierCurveTo(x, y + h * 2 / 3, x, y + h / 3, tlX, tlY);
context.closePath();
context.fillStrokeShape(this);
}
});
The problem is that i want to add more control points on each square edge. I want the canvas shape to look like this
The square above has 2 control points on each side in addition to 4 control points in the vertices, so each square has total 12 points .I want the squares in the js fiddle also to have 12 control points each.I can't understand where and what modifications to make so that each of the canvas squares has 12 control points..Please Help..

Related

JS calculting width and height for rotated rectangle

am trying to draw rectangles it different angles , initially i know rectangle position data it 0 degree , then sometime i get angle greater than 0 so i have to draw on that angle , for which i then have to rotate points. so currently i can rotate points but do not know to calculate width and height after rotation.
am doing it like ...
// Main rotation function
function rotate(originX, originY,pointX, pointY, angle) {
angle = angle * Math.PI / 180.0;
return {
x: Math.cos(angle) * (pointX-originX) - Math.sin(angle) * (pointY-originY) + originX,
y: Math.sin(angle) * (pointX-originX) + Math.cos(angle) * (pointY-originY) + originY
};
}
// initial Rectangle
ctx.fillStyle = "red";
var x = 200
var y = 200
var w = 80
var h = 20
var angle = 90 ;
ctx.fillRect(x, y, w, h);
// Calculate Center
var cx = (x + (w/2));
var cy = (y + (h/2));
// highlight Center
ctx.fillStyle = "black";
ctx.fillRect(cx,cy, 5, 5);
// Rotate starting x y at angle xxx
var r = rotate(cx,cy,x,y, angle - h );
// highlight roate points
ctx.fillStyle = "yellow";
ctx.fillRect(r.x, r.y, 5, 5);
// rotate Width and Height
var r2 = rotate(cx,cy,x+w,y+h, angle - h );
// highlight roate points
ctx.fillStyle = "green";
ctx.fillRect(r2.x, r2.y, 5, 5);
ctx.save();
so it end i rotated width and height which is ok for single line , but am interested in full rotation of width of height , like it 90 angle old with will become new height and old height will become new width. so any idea how to do it
Fiddle : https://jsfiddle.net/047txgox/
fixed it , hope will help someone
var x = 40
var y = 80
var w = 80
var h = 20
var angle = 120 ;
// Calculate Center
var cx = (x + (w/2));
var cy = (y + (h/2));
context.setTransform(1,0,0,1,0,0)
context.translate(cx, cy);
context.rotate(angle*Math.PI/180);
context.translate(-cx, -cy);
context.rect(x, y, w, h);
context.fillStyle = "#FF00FF";
context.fill();
// highlight Center
context.fillStyle = "black";
context.fillRect(cx,cy, 5, 5);
context.save();
Fiddle : http://jsfiddle.net/vorjcbz7/

how to to fit an image on a trapezium html canvas which I drew using path

[I want to add another image over the laptop screen`var ctx = myCanvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(13,28);
ctx.lineTo(237,7);
ctx.lineTo(285,105);
ctx.lineTo(73,151);
ctx.closePath();
ctx.lineWidth = 0.5;
ctx.strokeStyle = 'blue';
ctx.stroke();
//ctx.clip();
//ctx.rotate(-20*Math.PI/180);
var img = new Image;
img.onload = function()
{
var width = img.width;
var height = img.height;
ctx.drawImage(img, 0, 0, img.width, img.height,
0, 0, myCanvas.width, myCanvas.height);
}
};
img.src = reader.result;
`][1]
how do I skew the image to fit in the trapezium ctx that i have created
Why 2D API is 2D
The canvas 2D API is called a 2D API for a very good reason. It can only do 2D transformations.
Not 2D
The shape you have...
The above image annotates your shape. Lines A,C are not parallel.
2D best fit
The canvas 2D transformation can not fit a rectangle to that shape as it does not carry enough information to deal with the converging lines and how to scale the pixels as they converge.
The best you can do is an approximation
The next image bisects the shape using two lines. These lines will be used to create the transform that will best fit the shape using the 2D transformation.
We use the bisecting lines to define the x and y axis of the transform and use the length of each to determine the scale. The center point of the image is the center of one of the bisecting lines.
const ctx = canvas.getContext("2d");
const path = [13, 28, 237, 7, 285, 105, 73, 151];
const imageURL = "https://upload.wikimedia.org/wikipedia/commons/thumb/5/55/Sarcoramphus_papa_%28K%C3%B6nigsgeier_-_King_Vulture%29_-_Weltvogelpark_Walsrode_2013-01.jpg/675px-Sarcoramphus_papa_%28K%C3%B6nigsgeier_-_King_Vulture%29_-_Weltvogelpark_Walsrode_2013-01.jpg";
function setStyle(style){
Object.keys(style).forEach(key => ctx[key] = style[key]);
}
function drawPath(path, style) {
var i = 0;
setStyle(style);
ctx.beginPath();
ctx.moveTo(path[i++], path[i++]);
while (i < path.length) {
ctx.lineTo(path[i++], path[i++]);
}
ctx.closePath();
ctx.stroke();
}
function markPath(path, style, marks) {
var i = 0;
var len = path.length;
setStyle(style);
while (i < len) {
ctx.fillText(
marks[i >> 1],
(path[i] + path[((i++) + 2) % len]) / 2,
(path[i] + path[((i++) + 2) % len]) / 2
);
}
}
function bisectPath(path, modulo, style) {
var i = 0;
var len = path.length;
setStyle(style);
ctx.beginPath();
while (i < len) {
ctx.moveTo(
(path[i] + path[((i++) + 2) % len]) / 2,
(path[i] + path[((i++) + 2) % len]) / 2
);
i += modulo;
ctx.lineTo(
(path[i] + path[((i++) + 2) % len]) / 2,
(path[i] + path[((i++) + 2) % len]) / 2
);
i -= modulo + 2;
}
ctx.stroke();
}
// functions to create lines, get vectors, length and normals
function getLine(x1,y1,x2,y2){
return {
p1 : { x : x1 , y : y1 },
p2 : { x : x2 , y : y2 }
};
}
function getVec(line){
line.vec = {
x : line.p2.x - line.p1.x,
y : line.p2.y - line.p1.y
};
return line;
}
function getNormal(line){
line.len = Math.hypot(line.vec.x, line.vec.y);
line.norm = {
x : line.vec.x / line.len,
y : line.vec.y / line.len,
};
return line;
}
// create the 2 bisecting lines
var line1 = getNormal( getVec( getLine(
(path[0] + path[2]) / 2,
(path[1] + path[3]) / 2,
(path[4] + path[6]) / 2,
(path[5] + path[7]) / 2
)));
var line2 = getNormal( getVec( getLine(
(path[6] + path[0]) / 2,
(path[7] + path[1]) / 2,
(path[2] + path[4]) / 2,
(path[3] + path[5]) / 2
)));
// create an image to fit
var image = new Image;
image.src = imageURL;
image.onload = function(){
var w, h, sx, sy, cx, cy;
w = this.width;
h = this.height;
// calculate the image scales
sx = line2.len / w;
sy = line1.len / h;
// calculate the center
cx = (line1.p1.x + line1.p2.x) / 2;
cy = (line1.p1.y + line1.p2.y) / 2;
// now we have all the information needed to create the transformation
ctx.setTransform(
line2.norm.x * sx, // scale and direction of x axis
line2.norm.y * sx,
line1.norm.x * sy, // scale and direction of y axis
line1.norm.y * sy,
cx, cy, // the origin coordinate (0,0)
);
// Draw the image offset from its center by half its width and heigth
ctx.drawImage(this, -w / 2, -h / 2);
// reset the transformation
ctx.setTransform(1,0,0,1,0,0);
// draw the guides
drawPath(path, {
lineWidth : 0.5,
strokeStyle : "blue"
});
bisectPath(path, 2, {
lineWidth : 1,
strokeStyle : "white"
});
markPath(path, {
font : "18px arial",
textAlign : "center",
textBaseline : "middle",
fillStyle : "black"
}, ["A", "B", "C", "D"]);
}
<canvas id=canvas height=160></canvas>
That is the best you can do with the canvas 2D API. You could render each pixel calculating the 3D transform to fit the shape you have. Depending on the image size that can take some time (CPU) to do and the result will be OK.
WebGL for 3D
If you really need the 3D transform you should use webGL. There are plenty of examples on SO and the web on how to do that

How to find the control point of a quadratic curve using a parabola?

I can't figure out how to draw a parabola which is having a equation as y^2 = 4ax
So I have both end points i.e. P0, P2, however I can't figure out how to find control point to put in quadraticCurveTo() function.
To match a quadratic Bezier to this parabola formula and assuming origin is 0, you can use place the control point at -y0 or -y1 from one of the end points.
Example
First, lets rearrange the formula:
y2 = 4ax
to:
x = y2 / 4a
so we can plot from bottom down.
In this case we can simply boil down everything and use the inverse of y and mid x as control point.
The general principle though, is to find the tangents of the endpoints. Then where the lines from those intersect the control-point should be placed. If you want the mathematical steps on how to find the intersection I would recommend taking a look at Erik Man's answer here (which happened to be posted today but breaks down the math in much more details).
So, if we plot it within the window of a canvas (black is parabola, red is quadratic curve):
var ctx = document.querySelector("canvas").getContext("2d"),
w = ctx.canvas.width, h = ctx.canvas.height;
ctx.strokeStyle = "red";
ctx.lineWidth = 2;
ctx.translate(0, 6);
// formula
function f(y, a) {return y * y / (a * 4)};
var a = 80;
plotWindow();
function plotWindow() {
ctx.clearRect(0, -6, w, h);
ctx.fillStyle = "#000";
// plot parabola using formula
for(var i = 0; i < w; i++) {
var y = f(i - w * 0.5, a);
ctx.fillRect(i - 2, y - 2, 4, 4);
}
// plot parabola using quadratic curve:
var x0 = 0;
var y0 = f(-w * 0.5, a);
var x1 = w;
var y1 = f( w * 0.5, a);
var cx = x1 * 0.5; // control point is center for x
var cy = -y0; // control point is -y0 for y assuming top of parabola = 0
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.quadraticCurveTo(cx, cy, x1, y1);
ctx.stroke();
// plot a
ctx.fillStyle = "blue";
ctx.fillRect(cx - 3, a - 3, 6, 6);
ctx.fillText("a=" + a, cx + 6, a + 5)
}
// slider
document.querySelector("input").onchange = function() {
a = +this.value;
plotWindow();
};
canvas {border:1px solid #777}
<script src="https://cdn.rawgit.com/epistemex/slider-feedback/master/sliderfeedback.min.js"></script>
<label>a: <input type="range" min=10 max=172 value=80></label><br>
<canvas width=600 height=190></canvas>

Why properties previous circle in canvas is getting replaced with newer properties?

I have canvas, where I wanted to draw one inner and just above it one outer circle. First of some weired line is shown between in circle. Secondly, I want lineWidth of outer circle as 5, it is also making outer circle with as 5.
How to stop this?
My code,
<script>
function drawCircle() {
var circleCanvas = document.getElementById("myCircleCanvas");
var circle = circleCanvas.getContext("2d");
circle.beginPath();
var x = 95;
var y = 75;
var radius = 60;
circle.arc(x, y, radius, 0, 2 * Math.PI);
circle.stroke();
circle.fillStyle = '#505050';
circle.fill();
circle.fillStyle = '#fff';
var font = "bold " + radius / 3 + "px serif";
circle.font = font;
circle.fillText("AVAYA", x - x / 3, y - y / 5);
circle.fillStyle = '#000';
circle.fillText("CIE", x - x / 3 + 15, y - y / 5 + 25);
circle.arc(x, y, radius + 10, 0, 2 * Math.PI);
circle.lineWidth = 5;//just want to increase outer circle
circle.stroke(); //why drawing extra line
}
</script>
<canvas id="myCircleCanvas">Your browser does not support the HTML5 canvas tag.
</canvas>
If you want the two circles to be treated independently, close the path that you started and begin a new one before you draw the second circle. For instance:
function drawCircle() {
var circleCanvas = document.getElementById("myCircleCanvas");
var circle = circleCanvas.getContext("2d");
circle.beginPath();
var x = 95;
var y = 75;
var radius = 60;
circle.arc(x, y, radius, 0, 2 * Math.PI);
circle.stroke();
circle.fillStyle = '#505050';
circle.fill();
circle.fillStyle = '#fff';
var font = "bold " + radius / 3 + "px serif";
circle.font = font;
circle.fillText("AVAYA", x - x / 3, y - y / 5);
circle.fillStyle = '#000';
circle.fillText("CIE", x - x / 3 + 15, y - y / 5 + 25);
circle.closePath();
circle.beginPath();
circle.arc(x, y, radius + 10, 0, 2 * Math.PI);
circle.lineWidth = 5;//just want to increase outer circle
circle.stroke(); //why drawing extra line
circle.closePath();
}
http://jsfiddle.net/mudbo48j/1

Adding the checked pattern to a dynamically drawn 3 player chess board

Hello you bunch of wonderful geniuses!
I seem to have reached the peak of my knowledge here and was hoping someone could point me in the right direction.
I am trying to dynamically draw a 3 player chess/checkers board using JavaScript and the HTML 5 canvas.
So far I have came up with this;
var canvas = document.getElementById('canvas')
var length = canvas.height / 2;
var center = canvas.width / 2;
var rotation = ToRadians(60);
var angle = ToRadians(30);
var height = length * Math.cos(angle);
var width = length * Math.sin(angle);
while (rotation < Math.PI * 2) {
a = [center, length];
b = [a[0] - height * Math.sin(rotation), a[1] + height * Math.cos(rotation)];
c = [b[0] + width * Math.cos(rotation), b[1] + width * Math.sin(rotation)];
d = [c[0] + width * Math.sin(angle + rotation), c[1] - width * Math.cos(angle + rotation)];
//Drawing Main Frame and 6 segments
var c2 = canvas.getContext('2d');
c2.fillStyle = '#f00';
c2.strokeStyle = "#0f0";
c2.beginPath();
c2.moveTo(a[0], a[1]);
c2.lineTo(b[0], b[1]);
c2.lineTo(c[0], c[1]);
c2.lineTo(d[0], d[1]);
c2.closePath();
c2.stroke();
//Drawing first set of divides
ab1=[((a[0]+b[0])/2),((a[1]+b[1])/2)]
cd1=[((c[0]+d[0])/2),((c[1]+d[1])/2)]
ab2=[((a[0]+ab1[0])/2),((a[1]+ab1[1])/2)]
cd2=[((d[0]+cd1[0])/2),((d[1]+cd1[1])/2)]
ab3=[((b[0]+ab1[0])/2),((b[1]+ab1[1])/2)]
cd3=[((c[0]+cd1[0])/2),((c[1]+cd1[1])/2)]
c2.beginPath();
c2.moveTo(ab1[0], ab1[1]);
c2.lineTo(cd1[0], cd1[1]);
c2.moveTo(ab2[0], ab2[1]);
c2.lineTo(cd2[0], cd2[1]);
c2.moveTo(ab3[0], ab3[1]);
c2.lineTo(cd3[0], cd3[1]);
c2.stroke();
//Drawing second set of divides
bc1=[((c[0]+b[0])/2),((c[1]+b[1])/2)]
ad1=[((a[0]+d[0])/2),((a[1]+d[1])/2)]
bc2=[((c[0]+bc1[0])/2),((c[1]+bc1[1])/2)]
ad2=[((d[0]+ad1[0])/2),((d[1]+ad1[1])/2)]
bc3=[((b[0]+bc1[0])/2),((b[1]+bc1[1])/2)]
ad3=[((a[0]+ad1[0])/2),((a[1]+ad1[1])/2)]
c2.beginPath();
c2.moveTo(bc1[0], bc1[1]);
c2.lineTo(ad1[0], ad1[1]);
c2.moveTo(bc2[0], bc2[1]);
c2.lineTo(ad2[0], ad2[1]);
c2.moveTo(bc3[0], bc3[1]);
c2.lineTo(ad3[0], ad3[1]);
c2.stroke();
rotation += ToRadians(60);
}
function ToRadians(degrees) {
return degrees / (180 / Math.PI);
}
Fiddle: http://jsfiddle.net/yd7Wv/6529/
I'm quite please with the code so far but I've come to the point when I need to add the checked pattern and I am completely stumped. I literally have no idea how to go about doing this and so was wondering if someone could point me in the right direction.
I know there is a general consensus on here that people should show attempts to do it themselves but I simply can't!
Any pointers would be appreciated.
Cheers
What you are looking at is closely related to a quadrilateral transform.
You can look at one segment ("triangle") as a quadrant just distorted in perspective.
Fiddle demo
Which produces this result:
Lets begin with defining some variables we need for calculation and looping.
var w = canvas.width, // width
h = canvas.height, // height
cx = w * 0.5, // center of board
cy = h * 0.5,
r = cx * 0.9, // radius of board
pi2 = Math.PI * 2, // cache
segments = 6, // a hexagon based shape so 6
segment = pi2 / segments, // angle of each segment
hSegment = segment * 0.5, // half segment for center line
ul, ur, bl, br, // quad. corners
check = 0.25, // interpolation interval (one check)
yc = 0, xc = 0, // interpolation counters
toggle = false, // for color
x, y = 0, i = 0; // counters...
Lets define a single quadrilateral square by defining the corners of its outer boundaries:
First corner would be center of board so that one is simple:
var ul = {
x: cx,
y: cy}
Second corner would be upper right:
ur = {
x: cx + r * Math.cos(hSegment) * 0.865,
y: cy + r * Math.sin(hSegment) * 0.865
};
Third bottom right:
br = {
x: cx + r * Math.cos(segment),
y: cy + r * Math.sin(segment)
};
And last, bottom left:
bl = {
x: cx + r * Math.cos(hSegment + segment) * 0.865,
y: cy + r * Math.sin(hSegment + segment) * 0.865
};
If we draw out this shape we will get this:
Now that we have the corners we simply interpolate each line in the "square" by the check interval (0.25) which will give us in total 5 lines. We will only count 4 but we will also use the next line with the current value.
To interpolate two points we use a simple function which takes two points and a normalized value [0.0, 1.0]:
function getInt(p1, p2, t) {
return {
x: p1.x + (p2.x - p1.x) * t,
y: p1.y + (p2.y - p1.y) * t,
}
}
We create a loop to iterate through y and x points so we can do this in a systematic fashion:
for(y = 0, yc = 0; y < 4; y++) {
for(x = 0, xc = 0; x < 4; x++) {
// for upper lines (ul-ur), get first row:
var l1a = getInt(ul, bl, yc),
l1b = getInt(ur, br, yc),
l2a = getInt(ul, bl, yc + check),
l2b = getInt(ur, br, yc + check),
c1 = getInt(l1a, l1b, xc),
c2 = getInt(l1a, l1b, xc + check),
c3 = getInt(l2a, l2b, xc + check),
c4 = getInt(l2a, l2b, xc);
... draw shape ...
xc += check;
}
yc += check;
}
This section:
var l1a = getInt(ul, bl, yc), // current line [0, 3]
l1b = getInt(ur, br, yc),
l2a = getInt(ul, bl, yc + check), // next line [1, 4]
l2b = getInt(ur, br, yc + check),
calculates the interpolated points on the outer vertical lines. This gives us two new points which we then use to calculate a point on horizontal line and enables us to calculate each corner point for a "check":
c1 = getInt(l1a, l1b, xc), // corner 1 UL
c2 = getInt(l1a, l1b, xc + check), // corner 2 UR (next line)
c3 = getInt(l2a, l2b, xc + check), // corner 3 BR (next line)
c4 = getInt(l2a, l2b, xc); // corner 4 BL
Now we simply draw a polygon between those corners and fill:
ctx.beginPath();
ctx.moveTo(c1.x, c1.y);
ctx.lineTo(c2.x, c2.y);
ctx.lineTo(c3.x, c3.y);
ctx.lineTo(c4.x, c4.y);
ctx.fillStyle = toggle ? '#000' : '#fff';
To alter the color we use a toggle switch.
This single segment will look like this:
The next step is to draw all segments. We re-use the code above and simply rotate the canvas one segment for each time and do an extra toggle.
When all code is put together we get this:
for(; i < segments; i++) { // loop six segments
toggle = !toggle; // alter color each segment
// loop quadrilateral grid 4x4 cells (5x5 lines exclusive)
for(y = 0, yc = 0; y < 4; y++) {
for(x = 0, xc = 0; x < 4; x++) {
// for upper lines (ul-ur), get first row:
var l1a = getInt(ul, bl, yc),
l1b = getInt(ur, br, yc),
l2a = getInt(ul, bl, yc + check),
l2b = getInt(ur, br, yc + check),
c1 = getInt(l1a, l1b, xc),
c2 = getInt(l1a, l1b, xc + check),
c3 = getInt(l2a, l2b, xc + check),
c4 = getInt(l2a, l2b, xc);
ctx.beginPath();
ctx.moveTo(c1.x, c1.y);
ctx.lineTo(c2.x, c2.y);
ctx.lineTo(c3.x, c3.y);
ctx.lineTo(c4.x, c4.y);
ctx.fillStyle = toggle ? '#000' : '#fff';
ctx.fill();
toggle = !toggle;
xc += check;
}
yc += check; // next segment line
toggle = !toggle; // toggle per line as well
}
ctx.translate(cx, cy); // translate to center
ctx.rotate(segment); // rotate one segment
ctx.translate(-cx, -cy); // translate back
}
Now you can simply draw an outline if you wish and so forth.

Categories