I am trying to build a near-perfect circle with quadratic curves in canvas. I have this function for setting up points around a circle and connecting them with quadratic curves:
function calcPointsCircle(cx, cy, radius, dashLength) {
var n = radius / dashLength,
alpha = Math.PI * 2 / n,
i = -1;
while (i < n) {
var theta = alpha * i,
theta2 = alpha * (i + 1);
points.push({
x : (Math.cos(theta) * radius) + cx,
y : (Math.sin(theta) * radius) + cy,
ex : (Math.cos(theta2) * radius) + cx,
ey : (Math.sin(theta2) * radius) + cy,
py : (Math.sin(theta) * radius) + cy
});
i+=2;
}
}
for (i = 0; i < points.length; i++) {
var p = points[i];
ctx.strokeStyle = '#fff';
ctx.quadraticCurveTo(p.x, p.py, p.x, p.y);
ctx.stroke();
}
It works, but the lines are currently straight (which is obvious, since I am using the points x and y coordinates for the control point):
I am looking for a way to automatically calculate the poisitions for the control points based on the circle radius and the number of points... All help is more then welcome
Here's how to calculate the controls points of a set of quadratic curves which approximate a circle circumscribing a regular polygon.
Given:
A centerpoint, radius & sidecount.
For each side of the polygon, calculate:
3 points on a circumscribing circumference and then calculate the quadratic curve control point that causes the curve to pass through those 3 points:
The 2 points of the polygon side are 2 of the 3 points
Calculate the sweep angle between the 2 points of a side (var sweep)
Bisect the sweep angle (sweep/2)
Use trigonometry to calculate the point on the circumference midway between the 2 points of the side.
Calculate the middle control point:
// calc middle control point
var cpX=2*x1-x0/2-x2/2;
var cpY=2*y1-y0/2-y2/2;
Example code and a Demo:
// change sideCount to the # of poly sides desired
//
var sideCount=5;
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
ctx.lineWidth=2;
ctx.fillStyle=randomColor();
// save PI*2
var PI2=Math.PI*2;
// functions to calc a point on circumference of circle
var xx=function(a){return(cx+radius*Math.cos(a));}
var yy=function(a){return(cy+radius*Math.sin(a));}
// general interpolation function
var lerp=function(a,b,x){ return(a+x*(b-a)); }
// define the regular polygon
var cx=150;
var cy=150;
var radius=100;
// calc qCurve controls points and put in sides[] array
var sides=[];
for(var i=0;i<sideCount;i++){
sides.push(makeSide(i,sideCount));
}
// drawing and animating stuff
var percent=0;
var percentDirection=0.50;
$("#toShape").click(function(){
percentDirection=-0.50;
})
$("#toCircle").click(function(){
percentDirection=0.50;
})
animate();
// functions
function animate(){
requestAnimationFrame(animate);
drawSides(percent);
percent+=percentDirection;
if(percent>100){percent=100;}
if(percent<0){percent=0;}
}
function drawSides(pct,color){
ctx.clearRect(0,0,canvas.width,canvas.height);
if(pct==100){
ctx.beginPath();
ctx.arc(cx,cy,radius,0,PI2);
ctx.closePath();
ctx.fill();
}else{
ctx.beginPath();
ctx.moveTo(sides[0].x0,sides[0].y0);
for(var i=0;i<sideCount;i++){
var side=sides[i];
var cpx=lerp(side.midX,side.cpX,pct/100);
var cpy=lerp(side.midY,side.cpY,pct/100);
ctx.quadraticCurveTo(cpx,cpy,side.x2,side.y2);
}
ctx.fill();
}
}
// given a side of a regular polygon,
// calc a qCurve that approximates a circle
function makeSide(n,sideCount){
// starting & ending angles vs centerpoint
var sweep=PI2/sideCount;
var sAngle=sweep*(n-1);
var eAngle=sweep*n;
// given start & end points,
// calc the point on circumference at middle of sweep angle
var x0=xx(sAngle);
var y0=yy(sAngle);
var x1=xx((eAngle+sAngle)/2);
var y1=yy((eAngle+sAngle)/2);
var x2=xx(eAngle);
var y2=yy(eAngle);
// calc the control points to pass a qCurve
// through the 3 points
var dx=x2-x1;
var dy=y2-y1;
var a=Math.atan2(dy,dx);
var midX=lerp(x0,x2,0.50);
var midY=lerp(y0,y2,0.50);
// calc middle control point
var cpX=2*x1-x0/2-x2/2;
var cpY=2*y1-y0/2-y2/2;
return({
x0:x0, y0:y0,
x2:x2, y2:y2,
midX:midX, midY:midY,
cpX:cpX, cpY:cpY,
color:randomColor()
});
}
function randomColor(){
return('#'+Math.floor(Math.random()*16777215).toString(16));
}
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>
<button id="toShape">Animate to Shape</button>
<button id="toCircle">Animate to Circle</button><br>
<canvas id="canvas" width=300 height=300></canvas>
Related
I was trying to contribute to this post, HTML5 Canvas and Line Width
but it was deleted because it's not an official answer, because technically I'm also asking a question using the following code I get the same problem.
"I'm drawing line graphs on a canvas. The lines draw fine. The graph is scaled, every segment is drawn, color are ok, etc. My only problem is visually the line width varies. It's almost like the nib of a caligraphy pen. If the stroke is upward the line is thin, if the stroke is horizontal, the line is thicker.
My line thickness is constant, and my strokeStyle is set to black. I don't see any other properties of the canvas that affect such a varying line width but there must be.
"
<html>
<head>
<style>html{font-family:Verdana;}</style>
<script type="text/javascript">
var canvas ;
var context ;
var Val_max;
var Val_min;
var sections;
var xScale;
var yScale;
var Samsung = [21000,21000,23000,22000,22000,23000,23000];
function init() {
// set these values for your data
sections = 7;
Val_max = 25000;
Val_min = 10000;
var stepSize = 1500;
var columnSize = 75;
var rowSize = 75;
var margin = 10;
var xAxis = [""," Monday "," Tuesday"," Wednesday"," Thursday"," Friday"," Saturday"," Sunday"]//;
//
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
context.fillStyle = "#4d4d4d"
context.font = "10 pt Arial"
yScale = (canvas.height - columnSize - margin) / (Val_max - Val_min);
xScale = (canvas.width - rowSize) / sections;
context.strokeStyle="#4d4d4d"; // color of grid lines
context.beginPath();
// print Parameters on X axis, and grid lines on the graph
for (i=1;i<=sections;i++) {
var x = i * xScale;
context.fillText(xAxis[i], x,columnSize - margin);
context.moveTo(x, columnSize);
context.lineTo(x, canvas.height - margin);
}
// print row header and draw horizontal grid lines
var count = 0;
for (scale=Val_max;scale>=Val_min;scale = scale - stepSize) {
var y = columnSize + (yScale * count * stepSize);
context.fillText(scale, margin,y + margin);
context.moveTo(rowSize,y)
context.lineTo(canvas.width,y)
count++;
}
context.stroke();
context.lineWidth=20;
context.translate(rowSize,canvas.height + Val_min * yScale);
context.scale(1,-1 * yScale);
// Color of each dataplot items
context.strokeStyle="#2FBC3A";
plotData(Samsung);
}
function plotData(dataSet) {
// context.beginPath();
// context.moveTo(0, dataSet[0]);
// for (i=1;i<sections;i++) {
// context.lineTo(i * xScale, dataSet[i]);
// }
// context.stroke();
var love=0;
for (i=1;i<sections;i++) {
context.beginPath();
context.moveTo(love, dataSet[i-1]);
context.lineTo(i * xScale, dataSet[i]);
love=i*xScale;
context.stroke();
}
}
</script>
</head>
<body onLoad="init()">
<div align="center">
<canvas id="canvas" height="400" width="650">
</canvas>
<br>
<!--Legends for Dataplot -->
<span style="color:#4d4d4d"> Graph </span>
</div>
</body>
</html>
You are changing your context's scaleY in a non-uniform way.
So all the drawings after this operation will get shrunk on the Y axis.
To avoid that, apply this scaling only on your coordinates, at the time of drawing i.e
context.scale(1, -1 * yScale);
...
context.lineTo(x, y);
becomes
context.lineTo(x, y * -1 * yScale);
This way, your coordinate gets correctly scaled, but your stroke keeps its correct scale.
Also, you were drawing each segment separately, which would produce some holes in between of every segments, so I took the liberty of merging them in a single sub-path.
var canvas;
var context;
var Val_max;
var Val_min;
var sections;
var xScale;
var yScale;
var Samsung = [21000, 21000, 23000, 22000, 22000, 23000, 23000];
function init() {
// set these values for your data
sections = 7;
Val_max = 25000;
Val_min = 10000;
var columnSize = 75;
var rowSize = 75;
var margin = 10;
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
context.fillStyle = "#4d4d4d";
yScale = (canvas.height - columnSize - margin) / (Val_max - Val_min);
xScale = (canvas.width - rowSize) / sections;
context.lineWidth = 20;
context.translate(rowSize, canvas.height + Val_min * yScale);
//context.scale(1,-1 * yScale);
// ^-- Don't do that.
context.strokeStyle = "#2FBC3A";
plotData(Samsung);
}
function plotData(dataSet) {
var love = 0;
// make a single path from all the segments
context.beginPath();
for (var i = 0; i < sections; i++) {
// Here we scale the coordinate, not the drawings
context.lineTo(i * xScale, dataSet[i] * -1 * yScale);
love = i * xScale;
}
context.stroke();
}
init();
<canvas id="canvas" height="400" width="650">
</canvas>
Basically I need to create a falloff texture for given polygon. For instance this is the image I have
What I need to create is this, but with bevel gradient from white to black, consider the green part as gradient.
I've got the coordinates of all the vertices and the thickness of the bevel. I'm rendering using HTML5 2d canvas. Basically the most obvious solution would be to calculate every pixel's distance to the polygon and if it's within the thickness parameter, calculate the color and color the pixel. But that's heavy calculations and would be slow, even for smallest possible texture for my needs. So are there any tricks I can do with canvas to achieve this?
Just draw the polygon's outline at different stroke widths changing the colour for each step down in width.
The snippet shows one way of doing it. Draws 2 polygons with line joins "miter" and "round"
"use strict";
const canvas = document.createElement("canvas");
canvas.height = innerHeight;
canvas.width = innerWidth;
canvas.style.position = "absolute";
canvas.style.top = canvas.style.left = "0px";
const ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
// poly to draw
var poly = [0.1,0.2,0.4,0.5,0.2,0.8];
var poly1 = [0.6,0.1,0.9,0.5,0.8,0.9];
// convert rgb style colour to array
function rgb2Array(rgb){
var arr1 = rgb.split("(")[1].split(")")[0].split(",");
var arr = [];
while(arr1.length > 0){
arr.push(Number(arr1.shift()));
}
return arr;
}
// convert array to rgb colour
function array2rgb(arr){
return "rgb("+Math.floor(arr[0])+","+Math.floor(arr[1])+","+Math.floor(arr[2])+")"
}
// lerps array from to. Amount is from 0 # from 1 # to. res = is the resulting array
function lerpArr(from,to,amount,res){
var i = 0;
if(res === undefined){
res = [];
}
while(i < from.length){
res[i] = (to[i]-from[i]) * amount + from[i];
i++;
}
return res;
}
// draw gradient outline
// poly is the polygon verts
// width is the outline width
// fillStyle is the polygon fill style
// rgb1 is the outer colour
// rgb2 is the inner colour of the outline gradient
function drawGradientOutline(poly,width,fillStyle,rgb1,rgb2){
ctx.beginPath();
var i = 0;
var w = canvas.width;
var h = canvas.height;
ctx.moveTo(poly[i++] * w,poly[i++] * h);
while(i < poly.length){
ctx.lineTo(poly[i++] * w,poly[i++] * h);
}
ctx.closePath();
var col1 = rgb2Array(rgb1);
var col2 = rgb2Array(rgb2);
i = width * 2;
var col = [];
while(i > 0){
ctx.lineWidth = i;
ctx.strokeStyle = array2rgb(lerpArr(col1,col2,1- i / (width * 2),col));
ctx.stroke();
i -= 1;
}
ctx.fillStyle = fillStyle;
ctx.fill();
}
ctx.clearRect(0,0,canvas.width,canvas.height)
ctx.lineJoin = "miter";
drawGradientOutline(poly,20,"black","rgb(255,0,0)","rgb(255,255,0)")
ctx.lineJoin = "round";
drawGradientOutline(poly1,20,"black","rgb(255,0,0)","rgb(255,255,0)")
I'm trying to draw a curve in canvas with a linear gradient stoke style along the curve, as in this image. On that page there is a linked svg file that gives instructions on how to accomplish the effect in svg. Maybe a similar method would be possible in canvas?
A Demo: http://jsfiddle.net/m1erickson/4fX5D/
It's fairly easy to create a gradient that changes along the path:
It's more difficult to create a gradient that changes across the path:
To create a gradient across the path you draw many gradient lines tangent to the path:
If you draw enough tangent lines then the eye sees the curve as a gradient across the path.
Note: Jaggies can occur on the outsides of the path-gradient. That's because the gradient is really made up of hundreds of tangent lines. But you can smooth out the jaggies by drawing a line on either side of the gradient using the appropriate colors (here the anti-jaggy lines are red on the top side and purple on the bottom side).
Here are the steps to creating a gradient across the path:
Plot hundreds of points along the path.
Calculate the angle of the path at those points.
At each point, create a linear gradient and draw a gradient stroked line across the tangent of that point. Yes, you will have to create a new gradient for each point because the linear gradient must match the angle of the line tangent to that point.
To reduce the jaggy effect caused by drawing many individual lines, you can draw a smooth path along the top and bottom side of the gradient path to overwrite the jaggies.
Here is annotated code:
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// variables defining a cubic bezier curve
var PI2=Math.PI*2;
var s={x:20,y:30};
var c1={x:200,y:40};
var c2={x:40,y:200};
var e={x:270,y:220};
// an array of points plotted along the bezier curve
var points=[];
// we use PI often so put it in a variable
var PI=Math.PI;
// plot 400 points along the curve
// and also calculate the angle of the curve at that point
for(var t=0;t<=100;t+=0.25){
var T=t/100;
// plot a point on the curve
var pos=getCubicBezierXYatT(s,c1,c2,e,T);
// calculate the tangent angle of the curve at that point
var tx = bezierTangent(s.x,c1.x,c2.x,e.x,T);
var ty = bezierTangent(s.y,c1.y,c2.y,e.y,T);
var a = Math.atan2(ty, tx)-PI/2;
// save the x/y position of the point and the tangent angle
// in the points array
points.push({
x:pos.x,
y:pos.y,
angle:a
});
}
// Note: increase the lineWidth if
// the gradient has noticable gaps
ctx.lineWidth=2;
// draw a gradient-stroked line tangent to each point on the curve
for(var i=0;i<points.length;i++){
// calc the topside and bottomside points of the tangent line
var offX1=points[i].x+20*Math.cos(points[i].angle);
var offY1=points[i].y+20*Math.sin(points[i].angle);
var offX2=points[i].x+20*Math.cos(points[i].angle-PI);
var offY2=points[i].y+20*Math.sin(points[i].angle-PI);
// create a gradient stretching between
// the calculated top & bottom points
var gradient=ctx.createLinearGradient(offX1,offY1,offX2,offY2);
gradient.addColorStop(0.00, 'red');
gradient.addColorStop(1/6, 'orange');
gradient.addColorStop(2/6, 'yellow');
gradient.addColorStop(3/6, 'green')
gradient.addColorStop(4/6, 'aqua');
gradient.addColorStop(5/6, 'blue');
gradient.addColorStop(1.00, 'purple');
// draw the gradient-stroked line at this point
ctx.strokeStyle=gradient;
ctx.beginPath();
ctx.moveTo(offX1,offY1);
ctx.lineTo(offX2,offY2);
ctx.stroke();
}
// draw a top stroke to cover jaggies
// on the top of the gradient curve
var offX1=points[0].x+20*Math.cos(points[0].angle);
var offY1=points[0].y+20*Math.sin(points[0].angle);
ctx.strokeStyle="red";
// Note: increase the lineWidth if this outside of the
// gradient still has jaggies
ctx.lineWidth=1.5;
ctx.beginPath();
ctx.moveTo(offX1,offY1);
for(var i=1;i<points.length;i++){
var offX1=points[i].x+20*Math.cos(points[i].angle);
var offY1=points[i].y+20*Math.sin(points[i].angle);
ctx.lineTo(offX1,offY1);
}
ctx.stroke();
// draw a bottom stroke to cover jaggies
// on the bottom of the gradient
var offX2=points[0].x+20*Math.cos(points[0].angle+PI);
var offY2=points[0].y+20*Math.sin(points[0].angle+PI);
ctx.strokeStyle="purple";
// Note: increase the lineWidth if this outside of the
// gradient still has jaggies
ctx.lineWidth=1.5;
ctx.beginPath();
ctx.moveTo(offX2,offY2);
for(var i=0;i<points.length;i++){
var offX2=points[i].x+20*Math.cos(points[i].angle+PI);
var offY2=points[i].y+20*Math.sin(points[i].angle+PI);
ctx.lineTo(offX2,offY2);
}
ctx.stroke();
//////////////////////////////////////////
// helper functions
//////////////////////////////////////////
// calculate one XY point along Cubic Bezier at interval T
// (where T==0.00 at the start of the curve and T==1.00 at the end)
function getCubicBezierXYatT(startPt,controlPt1,controlPt2,endPt,T){
var x=CubicN(T,startPt.x,controlPt1.x,controlPt2.x,endPt.x);
var y=CubicN(T,startPt.y,controlPt1.y,controlPt2.y,endPt.y);
return({x:x,y:y});
}
// cubic helper formula at T distance
function CubicN(T, a,b,c,d) {
var t2 = T * T;
var t3 = t2 * T;
return a + (-a * 3 + T * (3 * a - a * T)) * T
+ (3 * b + T * (-6 * b + b * 3 * T)) * T
+ (c * 3 - c * 3 * T) * t2
+ d * t3;
}
// calculate the tangent angle at interval T on the curve
function bezierTangent(a, b, c, d, t) {
return (3 * t * t * (-a + 3 * b - 3 * c + d) + 6 * t * (a - 2 * b + c) + 3 * (-a + b));
};
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
I am working on doing something very similar, and I just wanted to add a couple things. markE's answer is great, but what he calls tangent lines to the curve, are actually lines normal or perpendicular to the curve. (Tangent lines are parallel, normal lines are perpendicular)
For my particular application, I am using a gradient across a line with transparency. In this case, it is important to get near pixel perfect gradient regions, as overlapping transparency will get drawn twice, changing the desired color. So instead of drawing a bunch of lines perpendicular to the curve, I divided the curve up into quadrilaterals and applied a linear gradient to each. Additionally, using these quadrilateral regions reduces the number of calls to draw you have to make, which can make it more efficient. You don't need a ton of regions to get a pretty smooth effect, and the fewer regions you use, the faster it will be able to render.
I adapted markE's code, so credit to him for that great answer. Here is the fiddle: https://jsfiddle.net/hvyt58dz/
Here is the adapted code I used:
// canvas related variables
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
// variables defining a cubic bezier curve
var PI2 = Math.PI * 2;
var s = {
x: 20,
y: 30
};
var c1 = {
x: 200,
y: 40
};
var c2 = {
x: 40,
y: 200
};
var e = {
x: 270,
y: 220
};
// an array of points plotted along the bezier curve
var points = [];
// we use PI often so put it in a variable
var PI = Math.PI;
// plot 400 points along the curve
// and also calculate the angle of the curve at that point
var step_size = 100/18;
for (var t = 0; t <= 100 + 0.1; t += step_size) {
var T = t / 100;
// plot a point on the curve
var pos = getCubicBezierXYatT(s, c1, c2, e, T);
// calculate the tangent angle of the curve at that point
var tx = bezierTangent(s.x, c1.x, c2.x, e.x, T);
var ty = bezierTangent(s.y, c1.y, c2.y, e.y, T);
var a = Math.atan2(ty, tx) - PI / 2;
// save the x/y position of the point and the tangent angle
// in the points array
points.push({
x: pos.x,
y: pos.y,
angle: a
});
}
// Note: increase the lineWidth if
// the gradient has noticable gaps
ctx.lineWidth = 2;
var overlap = 0.2;
var outside_color = 'rgba(255,0,0,0.0)';
var inside_color = 'rgba(255,0,0,0.7)';
// draw a gradient-stroked line tangent to each point on the curve
var line_width = 40;
var half_width = line_width/2;
for (var i = 0; i < points.length - 1; i++) {
var x1 = points[i].x, y1 = points[i].y;
var x2 = points[i+1].x, y2 = points[i+1].y;
var angle1 = points[i].angle, angle2 = points[i+1].angle;
var midangle = (angle1 + angle2)/ 2;
// calc the topside and bottomside points of the tangent line
var gradientOffsetX1 = x1 + half_width * Math.cos(midangle);
var gradientOffsetY1 = y1 + half_width * Math.sin(midangle);
var gradientOffsetX2 = x1 + half_width * Math.cos(midangle - PI);
var gradientOffsetY2 = y1 + half_width * Math.sin(midangle - PI);
var offX1 = x1 + half_width * Math.cos(angle1);
var offY1 = y1 + half_width * Math.sin(angle1);
var offX2 = x1 + half_width * Math.cos(angle1 - PI);
var offY2 = y1 + half_width * Math.sin(angle1 - PI);
var offX3 = x2 + half_width * Math.cos(angle2)
- overlap * Math.cos(angle2-PI/2);
var offY3 = y2 + half_width * Math.sin(angle2)
- overlap * Math.sin(angle2-PI/2);
var offX4 = x2 + half_width * Math.cos(angle2 - PI)
+ overlap * Math.cos(angle2-3*PI/2);
var offY4 = y2 + half_width * Math.sin(angle2 - PI)
+ overlap * Math.sin(angle2-3*PI/2);
// create a gradient stretching between
// the calculated top & bottom points
var gradient = ctx.createLinearGradient(gradientOffsetX1, gradientOffsetY1, gradientOffsetX2, gradientOffsetY2);
gradient.addColorStop(0.0, outside_color);
gradient.addColorStop(0.25, inside_color);
gradient.addColorStop(0.75, inside_color);
gradient.addColorStop(1.0, outside_color);
//gradient.addColorStop(1 / 6, 'orange');
//gradient.addColorStop(2 / 6, 'yellow');
//gradient.addColorStop(3 / 6, 'green')
//gradient.addColorStop(4 / 6, 'aqua');
//gradient.addColorStop(5 / 6, 'blue');
//gradient.addColorStop(1.00, 'purple');
// line cap
if(i == 0){
var x = x1 - overlap * Math.cos(angle1-PI/2);
var y = y1 - overlap * Math.sin(angle1-PI/2);
var cap_gradient = ctx.createRadialGradient(x, y, 0, x, y, half_width);
ctx.beginPath();
ctx.arc(x, y, half_width, angle1 - PI, angle1);
cap_gradient.addColorStop(0.5, inside_color);
cap_gradient.addColorStop(1.0, outside_color);
ctx.fillStyle = cap_gradient;
ctx.fill();
}
if(i == points.length - 2){
var x = x2 + overlap * Math.cos(angle2-PI/2);
var y = y2 + overlap * Math.sin(angle2-PI/2);
var cap_gradient = ctx.createRadialGradient(x, y, 0, x, y, half_width);
ctx.beginPath();
ctx.arc(x, y, half_width, angle2, angle2 + PI);
cap_gradient.addColorStop(0.5, inside_color);
cap_gradient.addColorStop(1.0, outside_color);
ctx.fillStyle = cap_gradient;
ctx.fill();
console.log(x,y);
}
// draw the gradient-stroked line at this point
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.moveTo(offX1, offY1);
ctx.lineTo(offX2, offY2);
ctx.lineTo(offX4, offY4);
ctx.lineTo(offX3, offY3);
ctx.fill();
}
//////////////////////////////////////////
// helper functions
//////////////////////////////////////////
// calculate one XY point along Cubic Bezier at interval T
// (where T==0.00 at the start of the curve and T==1.00 at the end)
function getCubicBezierXYatT(startPt, controlPt1, controlPt2, endPt, T) {
var x = CubicN(T, startPt.x, controlPt1.x, controlPt2.x, endPt.x);
var y = CubicN(T, startPt.y, controlPt1.y, controlPt2.y, endPt.y);
return ({
x: x,
y: y
});
}
// cubic helper formula at T distance
function CubicN(T, a, b, c, d) {
var t2 = T * T;
var t3 = t2 * T;
return a + (-a * 3 + T * (3 * a - a * T)) * T + (3 * b + T * (-6 * b + b * 3 * T)) * T + (c * 3 - c * 3 * T) * t2 + d * t3;
}
// calculate the tangent angle at interval T on the curve
function bezierTangent(a, b, c, d, t) {
return (3 * t * t * (-a + 3 * b - 3 * c + d) + 6 * t * (a - 2 * b + c) + 3 * (-a + b));
};
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.strokeRect(50,50,200,200);
ctx.translate(100,100);
ctx.scale(0.751,0.751);
ctx.translate(-100,-100);
ctx.strokeRect(50,50,200,200);
in canvas am drawing a rectangle,scaled and translated rectangle's start position is different from the original.
is there any calcutions to find the difference between the starting position of original rectangle and scaled rectangle.how can i get the difference or start position of scaled rectangle
Translations (moves), scales and rotations are all transforms.
Once you get a few transforms deep, tracking original vs transformed positions with arithmetic gets pretty scary!
Behind the scenes, html canvas uses matrix math to track where your transformed rectangle will be drawn.
You can also use the power of a matrix to track the difference between the top-left of your original rectangle and the top-left of your transformed rectangle.
The matrix is actually an array of 6 numbers. This is the “identity matrix” which represents one point in original untransformed space.
var matrix=[1,0,0,1,0,0];
Assume that your original rectangle is positioned at X/Y (in your case 50/50).
var x=50;
var y=50;
Instead of just ctx.translate(100,100), use this wrapper function to both translate and matrix track.
function translate(translateByX,translateByY){
matrix[4] += matrix[0] * translateByX + matrix[2] * translateByY;
matrix[5] += matrix[1] * translateByX + matrix[3] * translateByY;
ctx.translate(translateByX,translateByY);
}
After you’re back in original space, you have tracked where your transformed rectangle is by using the matrix.
You can get the position of your transformed X/Y (but in original space) like this:
function getXY(){
newX = x * matrix[0] + y * matrix[2] + matrix[4];
newY = x * matrix[1] + y * matrix[3] + matrix[5];
return({x:newX,y:newY});
}
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/yuaMs/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// test values
// objective: track this point into transformed space
var x=50;
var y=50;
// a transform matrix for point x/y
var matrix=[1,0,0,1,0,0];
// draw the first rectangle in blue
ctx.save();
ctx.beginPath();
ctx.strokeStyle="blue";
ctx.strokeRect(x,y,200,200);
// do transforms using the wrapped transform functions
// so the matrix is also tracked
translate(100,100);
scale(0.751,0.751);
translate(-100,-100);
// draw the second rectangle (now in transformed space)
ctx.beginPath();
ctx.strokeStyle="green";
ctx.strokeRect(x,y,200,200);
ctx.restore();
// Note: ctx.restore() has un-transformed our space
// get our original XY which has been transformed using the matrix
var transformed=getXY();
// draw a dot at the transformed x/y
ctx.beginPath();
ctx.arc(transformed.x,transformed.y,5,0,Math.PI*2,false);
ctx.closePath();
ctx.fillStyle="red";
ctx.fill();
// do the translate
// but also save the translate in the matrix
function translate(x,y){
matrix[4] += matrix[0] * x + matrix[2] * y;
matrix[5] += matrix[1] * x + matrix[3] * y;
ctx.translate(x,y);
}
// do the scale
// but also save the scale in the matrix
function scale(x,y){
matrix[0] *= x;
matrix[1] *= x;
matrix[2] *= y;
matrix[3] *= y;
ctx.scale(x,y);
}
// do the rotate
// but also save the rotate in the matrix
function rotate(radians){
var cos = Math.cos(radians);
var sin = Math.sin(radians);
var m11 = matrix[0] * cos + matrix[2] * sin;
var m12 = matrix[1] * cos + matrix[3] * sin;
var m21 = -matrix[0] * sin + matrix[2] * cos;
var m22 = -matrix[1] * sin + matrix[3] * cos;
matrix[0] = m11;
matrix[1] = m12;
matrix[2] = m21;
matrix[3] = m22;
ctx.rotate(radians);
}
// get the transformed point from the matrix
function getXY(vertex){
newX = x * matrix[0] + y * matrix[2] + matrix[4];
newY = x * matrix[1] + y * matrix[3] + matrix[5];
return({x:newX,y:newY});
}
}); // end $(function(){});
</script>
</head>
<body>
<p>Blue=drawn in original space</p>
<p>Green=drawn transformed space</p>
<p>Red=drawn in original space but tracked with matrix!</p>
<p id="newXY">Tracked x/y=</p>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
Once you've scaled your canvas, every operation that follows will be scaled, including the transforms.
So your second translate :
ctx.translate(-100,-100);
is in fact performing a translate by (-75.1, -75.1) in the original coordinate system.
I want to draw a vertical line that is resizable (based on the page content), but that appears to be hand drawn, rather than straight.
I'm currently thinking of using SVG or Canvas to achieve this. The line will run down the side of my webpage, hence needs to be scalable between the top and bottom of the container. How can I achieve this?
So you want to draw a line with jitter?
Try drawing a bunch of successive Bezier curves, with all of the Y-axis point parts equally spaced, and randomize the x values by some amount.
Here's an example to get you started:
var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');
function addJitteryBezier(fromx, fromy, tox, toy) {
var diffx = tox - fromx;
var diffy = toy - fromy;
var neg = Math.random()*diffy/5; // so the x value can go positive or negative from the typical
ctx.bezierCurveTo(
-neg + fromx + 2*(Math.random()*diffy/8), fromy + .3*diffy,
-neg + fromx + 2*(Math.random()*diffy/8), fromy + .6*diffy,
tox, toy
);
}
ctx.beginPath();
ctx.moveTo(50,0);
var i = 0;
while (i < 500) {
addJitteryBezier(50, i, 50, i+50);
i+= 50;
}
ctx.stroke();
<canvas id="canvas1" width="500" height="500"></canvas>
http://jsfiddle.net/GfGVE/9/