How to extract properties from html Canvas CanvasGradient and CanvasPattern objects - javascript

Given: an html canvas context:
Example code: http://jsfiddle.net/m1erickson/wJ67M/
This code creates a CanvasGradient object in the gradient variable.
var gradient=context.createLinearGradient(0,0,300,150);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'white');
This code creates a CanvasGradient object in the gradient variable.
var gradient=context.createRadialGradient(150,150,30, 150,150,100);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'white');
This code creates a CanvasPattern object in the pattern variable.
var pattern=context.createPattern(myImage,'repeat');
Is there a programatic way of extracting the properties from these objects after they are created (not by referring back to the JS code that created them)?
Wanted:
The linear gradients line segment (0,0,300,150) and its colorstops (0,red,1,white).
The radial gradients circles (150,150,30, 150,150,100) and its colorstops (0,red,1,white).
The patterns image and repeat properties.
Thanks for any thoughts!

The canvas specs does not grant access to the inner gradient or pattern properties, just like, as you also know, one cannot get access to the transform matrix.
So the only solution is to inject CanvasRenderingContext2D, CanvasGradient and CanvasPattern prototypes to store, in the created objects, the values used to create them.
So for the Canvas, you can write something like :
// save native linear gradient function
var nativeCreateLinearGradient = CanvasRenderingContext2D.prototype.createLinearGradient ;
// redefine createLinearGradient with a function that stores creation data
// new properties : gradientType, x0, y0, x1, y1, colorStops
CanvasRenderingContext2D.prototype.createLinearGradient = function(x0, y0, x1, y1) {
// actually create the gradient
var newGradient = nativeCreateLinearGradient.apply(this,arguments);
// store creation data
newGradient.gradientType = 0 ; // 0 for linear, 1 for radial
newGradient.x0 = x0; newGradient.y0 = y0;
newGradient.x1 = x1; newGradient.y1 = y1;
newGradient.colorStops = [];
return newGradient;
};
And for the Gradient :
var dummyContext = document.createElement('canvas').getContext('2d');
var nativeAddColorStop = CanvasGradient.prototype.addColorStop;
CanvasGradient.prototype.addColorStop = function (offset, color) {
// evaluate offset (to avoid reference issues)
offset = +offset;
// evaluate color (to avoid reference issues)
dummyContext.fillStyle = color;
color = dummyContext.fillStyle ;
// store color stop
this.colorStops.push([offset, color]);
// build the real gradient
nativeAddColorStop.call(this, offset, color);
return this;
};
You can do this in a very similar way for the radial gradient, and for the pattern you might want to copy the image, which type is CanvasImageSource )

Related

Javascript canvas draw line strange behavior using algorithm

There are plenty of examples on how to draw lines on canvas, in js.
But for only educational purposes i want to draw line using algorithm. basically method gets two Vector2 points, from them it finds middle point, then it continues like that recursively until minimum distance of 2 pixels is reached.
I have DrawPoint method to basically draw 1 point on canvas, and DrawLine method that does all the job.
For now I have 2 problems:
1: points are not colored red, as they should be.
2:
It doesnt look like a line.
For Vector2 i used "Victor.js" plugin, and it seems to be working well.
this is code i have:
JS:
var point2 = new Victor(100, 100);
var point3 = new Victor(150, 150);
DrawLine(point2, point3);
function DrawLine(vec0, vec1)
{
var point0 = new Victor(vec0.x, vec0.y);
var point1 = new Victor(vec1.x, vec1.y);
var dist = point1.distance(point0);
if (dist < 2)
return;
//this is how it should look like in c# var middlePoint = point0 + (point1 - point0)/2; But looks like i cant just divide by 2 using victor js because i can only divide vector by vector.
var middlePoint = point0.add(point1.subtract(point0).divide(new Victor(2,2)));
DrawPoint(middlePoint);
DrawLine(point0, middlePoint);
DrawLine(middlePoint, point1);
}
function DrawPoint(point){
var c = document.getElementById("screen");
var ctx = c.getContext("2d");
ctx.fillStyle = "FF0000";
ctx.fillRect(point.x, point.y, 3,1);
}
I really appreciate any help you can provide.
The victor.js documentation shows that most functions of Victors do not return new Victors, but operate on the current instance. In a way, v1.add(v2) is semantically more like v1 += v2 and not v1 + v2.
The problem is with calculating the midpoint. You could use the mix() method, which blends two vectors with a weight. You must clone() the Victor first, otherwise point0will be midofied:
var middlePoint = point0.clone().mix(point1, 0.5);
If you don't change the original Vectors, you don't need to create new instances of Victors from the arguments, you can use the arguments directly:
function DrawLine(point0, point1)
{
var dist = point1.distance(point0);
if (dist < 2) return;
var middlePoint = point0.clone().mix(point1, 0.5);
DrawPoint(middlePoint);
DrawLine(point0, middlePoint);
DrawLine(middlePoint, point1);
}
Finally, as Sven the Surfer has already said in a comment, "FF0000" isn't a valid colour. Use "#FF0000", note the hash mark, or one of the named web colours such as "crimson".

Use of isPointInPath() to reference Path2D objects?

So I've gotten pretty excited about the introduction of Canvas Paths as standard objects in contemporary browsers, and have been trying to see how much mileage I can get out of this new-ish feature. However, my understanding of how these objects interact with the isPointInPath() method (and possibly other path-based methods) is apparently somewhat flawed.
As demonstrated in the first two test functions below, I can get the drawn paths to be recognized by the isPointInPath() method. However, when I define the paths as an object, the method ceases to work (even though the path objects can be recognized for other purposes such as filling).
function startGame(){ //Initiating Environment Variables
gamemap = document.getElementById("GameMap")
ctx = gamemap.getContext("2d")
testCircleBounds()
testVarCircleBounds()
testObjCircleBounds()
testMultiObjCircleBounds()
}
function testCircleBounds() { //Minimalist Test of Path Methods
ctx.beginPath()
ctx.arc(250,250,25,0,2*Math.PI)
console.log(ctx.isPointInPath(250,250)) //point in path detected
ctx.closePath()
console.log(ctx.isPointInPath(250,250)) //point in path still detected
ctx.stroke()
ctx.fillStyle = "yellow"
ctx.fill() //fills great
}
function testVarCircleBounds() { //Test of Path Methods with Variables
x_cen = 250; y_cen = 250; rad = 15
ctx.beginPath()
ctx.arc(x_cen,y_cen,rad,0,2*Math.PI)
ctx.closePath()
console.log(ctx.isPointInPath(x_cen,y_cen)) //true yet again
ctx.stroke()
ctx.fillStyle = "orange"
ctx.fill() //also fills great
}
function testObjCircleBounds() { //Test of Path Methods with Single Stored Path Object
x_cen = 250; y_cen = 250; rad = 10
ctx.beginPath()
lonely_node = new Path2D()
lonely_node.arc(x_cen,y_cen,10,0,2*Math.PI)
ctx.closePath()
console.log(ctx.isPointInPath(x_cen,y_cen)) //point in path not found!
ctx.stroke(lonely_node)
ctx.fillStyle = "red"
ctx.fill(lonely_node) //but ctx.fill notices the path just fine
}
function testMultiObjCircleBounds(){ //Test of Paths Methods with Multi-Object Referencing
nodes = [] //initializes set of nodes as array
for (i=0; i<25; i++) { //generates 25 nodes
nodes[i] = new Path2D() //defines each node as Path object in the array
node = nodes[i]
//Places Nodes along the 'horizon' of the map
x_cen = 20*i + 10
y_cen = 100
ctx.beginPath(node) //"node" argument probably not helping?
node.arc(x_cen,y_cen,8,0,2*Math.PI)
console.log(ctx.isPointInPath(x_cen,y_cen)) //still returns false!
ctx.closePath(node)
ctx.stroke(node)
console.log(ctx.isPointInPath(x_cen,y_cen)) //arrgh!!
}
// Fill can also be selectively applied to referenced path objects
for (i=0; i<25; i=i+2) {
ctx.fill(nodes[i])
}
}
<!DOCTYPE html>
<html>
<head>
<title>Wrap Around Beta</title>
<script src="Circuity_PathObjectTest.js"></script>
</head>
<body onload='startGame()'>
<canvas id="GameMap" width="500" height="500" style="border:1px solid #000000"></canvas>
</body>
</html>
Is this fundamentally the wrong way to think about Path2D objects and to record 'hit' areas on a canvas? If so, is there another technique (saving the canvas context for each path drawn or something along that vein) that would produce the desired effect?
You must send a reference to the Path2D being tested into isPointInPath:
ctx.isPointInPath( lonely_node, x_cen, y_cen )

How to set id for drawn canvas shape?

I see this question and I dont know how I can set id for each circles and access them from javascript codes and css codes? (e.g. click)
You can solve this by defining click objects when drawing the circles. Inside the loop drawing the circles (ref. the fiddle made by #MonicaOlejniczak):
...
// push circle info as objects:
circles.push({
id: i + "," + j, // some ID
x: x,
y: y,
radius: radius
});
...
Then:
add a click handler to canvas
correct mouse position
loop through the objects finding if (x,y) is inside the circle:
Function example:
canvas.onclick = function(e) {
// correct mouse coordinates:
var rect = canvas.getBoundingClientRect(), // make x/y relative to canvas
x = e.clientX - rect.left,
y = e.clientY - rect.top,
i = 0, circle;
// check which circle:
while(circle = circles[i++]) {
context.beginPath(); // we build a path to check with, but not to draw
context.arc(circle.x, circle.y, circle.radius, 0, 2*Math.PI);
if (context.isPointInPath(x, y)) {
alert("Clicked circle: " + circle.id);
break;
}
}
};
You can optionally use math instead of the isPointInPath(), but the latter is simpler and is fast enough for this purpose.
Modified version of the same fiddle
You can't set an ID on something that has been drawn to a canvas.
The element on its own is just a bitmap and does not provide information about any drawn objects.
If you need to interact with the items inside the canvas you need to manually keep a reference to where everything is drawn, or use a system like "object picking" or using the built in hit regions.

Raphael JS nested transformations

I have several nested sets in Raphael JS that I want to use like layers in photoshop. That is: objects in sets may have their own transformations, and being places to a set they become relatively positioned in it. And set may have it's own transformation too.
Now it seems when a set transformation is applied, it just performs it to each element separately and with absolute position relatively the page.
With that mechanics I run into such simple problem: I have the set and the rectangle in it. Then I resize rectangle with scale(0.5,0.5,0,0); And then I want to drag the entire set. I perform dragging with set.translate(x,y). As the result I get rectangle that moves twice slower than other non-scaled items.
var rdr = this;
this.paper = Raphael(0,0,1000,1000);
this.set = this.paper.set();
this.set.push(this.paper.rect(0,0,100,100)); // non-scaled rectangle
this.set.push(this.paper.rect(0,0,100,100).scale(0.5,0.5,0,0)); // scaled rectangle
$("body").bind("mousedown.RDR",function(e) {
var ox = e.pageX;
var oy = e.pageY;
$("body").bind("mousemove.RDR",function(e) {
rdr.set.translate(e.pageX-ox,e.pageY-oy);
ox = e.pageX;
oy = e.pageY;
}).bind("mouseup.RDR",function() {
$("body").unbind("mouseup.RDR").unbind("mousemove.RDR");
});
});
How should I correct this code to make my rectangles move with the same speed?
Theoretically all that I need to move a set of objects simultaneously is a way to control the order of transformations. I haven't found built in solution so there's a little hack that inserts translation of a set "BEFORE" transformations that're already applied to elements:
Raphael.el.translateBefore = function(x,y) {
var matrix = this.matrix;
var transform = matrix.toTransformString();
transform = ("t"+x.toString()+","+y.toString()) + "," + transform;
this.transform(transform);
return this;
}
this.paper = Raphael(this.containerId,this.paperWidth,this.paperHeight);
// добавляем метод для raphael.set через жопу, не нашел нормальный способ
this.paper.set().__proto__.translateBefore = function(x,y) {
_.each(this,function(el) {
el.translateBefore(x,y);
});
return this;
}
http://raphaeljs.com/reference.html#Element.transform
// if you want you can append or prepend transformations
el.transform("...t50,50");
el.transform("s2...");

Change canvas gradient object's properties

var i = 0;
var x = 960;
var y = 540;
var interval = window.setInterval(render, 100);
function render()
{
gradient = cxt.createRadialGradient(x, y, i, x, y, i+10);
gradient.addColorStop(0, "rgba(0,0,0,0)");//white inside
gradient.addColorStop(.4, "rgba(255,255,255,1)");//white inside
gradient.addColorStop(.6, "rgba(255,255,255,1)");//white inside
gradient.addColorStop(1, "rgba(0,0,0,0)");//fade to transparent black outside;
cxt.fillStyle= "#000000";
cxt.fillRect(0,0,WIDTH,HEIGHT);
cxt.fillStyle = gradient;
cxt.fillRect(0,0,WIDTH,HEIGHT);
cxt.fill();
cxt.drawImage(logo,0,0);
i+=5;
}
I'm trying to animate a feathered loop expanding.
This code is pretty inefficient, because I'm using the constructor to change as single property each loop. How can I change a single property that is passed as a parameter to the constructor?
From the Canvas specs...
interface CanvasGradient {
// opaque object
void addColorStop(in float offset, in DOMString color);
};
...and earlier it says about fillStyle and strokeStyle...
On getting, if the value is a color,
then the serialization of the color
must be returned. Otherwise, if it is
not a color but a CanvasGradient or
CanvasPattern, then the respective
object must be returned. (Such objects
are opaque and therefore only useful
for assigning to other attributes or
for comparison to other gradients or
patterns.)
Lastly, introspecting a gradient just reveals the addColorStop function.
So I think the constructor is the only place those values can be set; but are you sure the constructor is really slowing it down? If your animation is slow maybe it's something else. Have you timed it?

Categories

Resources