I'm currently playing with Kinetic.JS. I have drawn a rather crude UFO-like shape in two parts, a hull (red) and a disc (grey).
Demo - JSBin
Question: how come when I later arrange the shape ordering so the hull is above the disc, the disc bizarrely goes from grey to the hull's red?
Uncomment the moveToTop() line at the bottom of my JSBin to see what I mean. Here's the pertinent (condensed) code.
//ship hull
var hull = new Kinetic.Shape({
drawFunc: function(ctx) {
ctx.arc(game_dims.w / 2, game_dims.h * 0.6, game_dims.h * 0.45, 0, Math.PI, true);
this.fill(ctx);
},
fill: 'red'
});
//ship disc
var disc = new Kinetic.Circle({
x: game_dims.w / 2,
y: game_dims.h * 0.6,
radius: {x: game_dims.w * 0.45, y: 30},
fill: '#888'
});
//draw
layer.add(hull);
layer.add(disc);
stage.add(layer);
//post-production
hull.moveToTop(); // <-- weirdness - changes disc colour!?
layer.draw();
I am aware I could draw the two shapes in reverse order to get the desired order, but that is not what I want with this question - I'm interested in rearrangement of order after drawing.
Thanks in advance
Your draw function of the hull needs to tell the context it's drawing a new path:
function(ctx) {
ctx.beginPath();
ctx.arc(...);
this.fill(ctx);
}
By adding the beginPath() command you are telling the context that you are not in fact adding to the previous path, but drawing a new one instead. This is also what makes this.fill() fill the previous shape with red, because in your example the context is still referring to the disc when it attempts to fill it
Related
So I have been working on a project for a while, when drawing in canvas is required. I'm using KineticJS lib for the whole canvas stuff but I think it is not the source of my problem. So one part of the project is that I need to draw lines to connect elements and sometimes these lines are drawn on one another so one covers the other and here comes my problem that it not just simply covers it but it makes thicker and thicker and you can really see the difference.
If anyone met the same problem could you please help me? :) Is it a normal behavior? Can I disable it? Because calculating of the already drawn lines and checking if it was drawn before is not really an option here..
Pic of the lines:
(The two on the top are drawn only once, but the two at the bottom are drawn multiple times.
Thanks in advance!
EDIT 1
So here is the code part where I draw these lines.
var stage = new Kinetic.Stage({
container: _this.containerId,
width: _this.options.fitWindow?window.innerWidth:0,
height: _this.options.fitWindow?window.innerHeight-5-61:0,
draggable: true,
dragDistance: 10
});
var layer = new Kinetic.Layer();
stage.add(layer);
var conns = new Kinetic.Group(),
points = [0,10,100,10];
conns.add( new Kinetic.Line({
points: points,
strokeWidth: 1,
stroke: '#db9e36',
listening: false
}) );
conns.add( new Kinetic.Line({
points: points,
strokeWidth: 1,
stroke: '#db9e36',
listening: false
}) );
layer.add( conns );
stage.draw();
You are using the same points variable to create 2 lines.
// one set of points
points = [0,10,100,10];
This causes your 2 lines to be drawn in the same place.
Therefore, your thicker line is really multiple lines drawn on top of each other.
// first line using points variable
conns.add( new Kinetic.Line({
points: points,
strokeWidth: 1,
stroke: '#db9e36',
listening: false
}) );
// second line using the same points variable
// this causes your 2 identical lines to be drawn--resulting in 1 thicker line
conns.add( new Kinetic.Line({
points: points,
strokeWidth: 1,
stroke: '#db9e36',
listening: false
}) );
I am very new to canvas drawings. I am trying to apply gradient along the arc of a circle. I can get it to appear fine when I draw the arc with center offset from the context coordinates. Lets say centerX and centerY denote the center of the canvas. I can get a gradient arc using context.arc(centerX, centerY, radius, ......).
Working example: http://jsfiddle.net/m5Pmb/
But when I try to draw the arc around the context coordinates, the gradient disappears. For example, I take the above working jsfiddle example, do context.translate(centerX, centerY), then do context.arc(0,0,radius,......), the resulting arc does not have any gradient on it.
Example here: http://jsfiddle.net/N6NMB/
In my case, I need to spin the resulting circle around its axis using context.rotate(), so I must translate to the center and draw circle around (0,0). But I cant understand why the gradient disappears when trying to draw arc around context's (0,0) point. Any insight would be really helpful.
Since you are translating the context centerX and centerY are not where you think anymore.
When you translate the context you are saying you want that x and y to be the new 0,0. So now your 0,0 is in the center of the canvas, so centerX and centerY are offset by themselves putting them further away from the center.
One method you can use is the following
var grad = context.createLinearGradient(
-radius,
radius / 2,
radius,
radius / 2
);
live Demo
The above works because its called after you translate the context, so centerX and centerY (like I said previously) are 0,0 meaning they don't need to be referenced in that function.
The gradients that you create will be painted with the actual transform in use.
So if you are not using any transform, no need to wonder anything : define your gradient where you're about to draw, and you'll be fine.
If you are using transform, you must think of the coordinates of the gradient as relative to the point/angle/scale when you'll use them.
To explain further, i modified your example and used a radial gradient.
I created a normalized gradient : it is defined in between 0.0 and 1.0 radius, meaning it will have its x and y in [-1; 1].
var eyeGrad = context.createRadialGradient(0, 0, 0, 0, 0, 1.0);
Then to use the gradient i must :
1) translate to be in the center of the figure i want to draw.
2) scale to have normalized coordinates.
function drawEye(x, y, r) {
context.save();
//translate context to center
context.translate(x, y);
// scale to radius
context.scale(r, r);
context.beginPath();
// draw an arc with radius of 1
context.arc(0, 0, 1, 0, 2 * Math.PI, false);
context.fillStyle = eyeGrad;
context.fill();
context.restore();
}
fiddle is here :
http://jsfiddle.net/gamealchemist/N6NMB/3/
Result for :
drawEye(100, 100, 40);
drawEye(250, 120, 20);
I've seen examples where it's possible to tween a rectangle using scaleX, but I can't find anything that tweens a circle. (The "circle" that I'm drawing is actually a donut shape and I want the outside circle to be the one that is tweened).
var resizeVar:Number = 75;
myCircle.graphics.drawCircle((myCircle.width/2), (myCircle.height/2), resizeVar);
myCircle.graphics.drawCircle((myCircle.width/2), (myCircle.height/2), 75);
I tried doing it this way, but this throws lots of errors. I don't think it's possible this way:
TweenMax.to(myCircle, 2, {resizeVar:150, ease:SlowMo.ease.config(1, 0)});
Normally with display objects, it is done this way. It doesn't work with this "donut" though:
TweenMax.to(myRectangle, 2, {scaleX:1.5, scaleY:1.5 ease:SlowMo.ease.config(1, 0)});
So my question is, how can I tween the radius size of my outside circle?
EDIT: This is how the donut is being drawn, so the resizeVar needs to change from 75 to 150.
var myCircle:Sprite = new Sprite();
myCircle.graphics.beginFill(0xbbbbbb);
myCircle.graphics.drawCircle(0, 0, 150); // this is what should be tweening/scaling
myCircle.graphics.drawCircle(0, 0, 75); // this should stay the same
myCircle.graphics.endFill();
addChild(myCircle);
You should be able to tween the scaleX and scaleY properties of ANY displayObject:
var radius:Number = 75;
var myCircle:Sprite = new Sprite();
myCircle.graphics.beginFill(0);
myCircle.graphics.drawCircle(radius/2, radius/2, radius);
myCircle.graphics.endFill();
addChild(myCircle);
TweenMax.to(myCircle, 2, {scaleX:2, scaleY:2, ease:SlowMo.ease.config(1,0)});
EDIT
This is how you would scale just the outside of the donut:
var resizeObject:Object = { innerRadius:75, outerRadius:150 };
myCircle = new Sprite();
myCircle.graphics.beginFill(0xbbbbbb);
myCircle.graphics.drawCircle(0, 0, resizeObject.outerRadius);
myCircle.graphics.drawCircle(0, 0, resizeObject.innerRadius);
myCircle.graphics.endFill();
addChild(myCircle);
TweenMax.to(resizeObject, 2, {outerRadius:300, ease:SlowMo.ease.config(1,0), onUpdate:updateCircle, onUpdateParams:[resizeObject]});
function updateCircle(resizeObject:Object):void
{
myCircle.graphics.clear();
myCircle.graphics.beginFill(0xbbbbbb);
myCircle.graphics.drawCircle(0, 0, resizeObject.outerRadius);
myCircle.graphics.drawCircle(0, 0, resizeObject.innerRadius);
myCircle.graphics.endFill();
}
The reason it works with the rectangle is that you are changing the scale of the rectangle. When you change the scale Flash Player adjusts the scale of the display object containing your graphics.
However, with the circle, you are trying to change the radius of the circle. The radius is only used when you draw the circle with the drawCircle() method. One way to tween the radius is to use your tween to re-draw the circle many times (not that ideal).
To re-draw the circle with a new radius, you can use the onUpdate callback that TweenMax offers:
TweenMax.to(myCircle, 2, {resizeVar:150, onUpdate: onUpdateCallback, onUpdateParams: [resizeVar] });
function onUpdateCallback(radius):void
{
myCircle.graphics.drawCircle(myCircle.graphics.drawCircle((myCircle.width/2), (myCircle.height/2), radius);
}
[Edit]
Note, I've added some params that you need to pass to the onUpdateCallback() function. I've also modified the function to add a radius parameter, and then use the radius when drawing the circle.
In regards to "trying to change the outside circle of this donut", this may be more complex. You might need to draw both circles of the donut. You might need to also call graphics.clear() before you draw the circle.
However, perhaps the answer from #Marcela is better, just change the scaleX and scaleY of the object you've already drawn. But if you need to get to a specified radius, the only way to do that is by re-drawing the circle(s) on each interval of the tween.
This question already has answers here:
What's the best way to set a single pixel in an HTML5 canvas?
(14 answers)
Closed 7 years ago.
Drawing a line on the HTML5 canvas is quite straightforward using the context.moveTo() and context.lineTo() functions.
I'm not quite sure if it's possible to draw a dot i.e. color a single pixel. The lineTo function wont draw a single pixel line (obviously).
Is there a method to do this?
For performance reasons, don't draw a circle if you can avoid it. Just draw a rectangle with a width and height of one:
ctx.fillRect(10,10,1,1); // fill in the pixel at (10,10)
If you are planning to draw a lot of pixel, it's a lot more efficient to use the image data of the canvas to do pixel drawing.
var canvas = document.getElementById("myCanvas");
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var ctx = canvas.getContext("2d");
var canvasData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
// That's how you define the value of a pixel
function drawPixel (x, y, r, g, b, a) {
var index = (x + y * canvasWidth) * 4;
canvasData.data[index + 0] = r;
canvasData.data[index + 1] = g;
canvasData.data[index + 2] = b;
canvasData.data[index + 3] = a;
}
// That's how you update the canvas, so that your
// modification are taken in consideration
function updateCanvas() {
ctx.putImageData(canvasData, 0, 0);
}
Then, you can use it in this way :
drawPixel(1, 1, 255, 0, 0, 255);
drawPixel(1, 2, 255, 0, 0, 255);
drawPixel(1, 3, 255, 0, 0, 255);
updateCanvas();
For more information, you can take a look at this Mozilla blog post : http://hacks.mozilla.org/2009/06/pushing-pixels-with-canvas/
It seems strange, but nonetheless HTML5 supports drawing lines, circles, rectangles and many other basic shapes, it does not have anything suitable for drawing the basic point. The only way to do so is to simulate a point with whatever you have.
So basically there are 3 possible solutions:
draw point as a line
draw point as a polygon
draw point as a circle
Each of them has their drawbacks.
Line
function point(x, y, canvas){
canvas.beginPath();
canvas.moveTo(x, y);
canvas.lineTo(x+1, y+1);
canvas.stroke();
}
Keep in mind that we are drawing to South-East direction, and if this is the edge, there can be a problem. But you can also draw in any other direction.
Rectangle
function point(x, y, canvas){
canvas.strokeRect(x,y,1,1);
}
or in a faster way using fillRect because render engine will just fill one pixel.
function point(x, y, canvas){
canvas.fillRect(x,y,1,1);
}
Circle
One of the problems with circles is that it is harder for an engine to render them
function point(x, y, canvas){
canvas.beginPath();
canvas.arc(x, y, 1, 0, 2 * Math.PI, true);
canvas.stroke();
}
the same idea as with rectangle you can achieve with fill.
function point(x, y, canvas){
canvas.beginPath();
canvas.arc(x, y, 1, 0, 2 * Math.PI, true);
canvas.fill();
}
Problems with all these solutions:
it is hard to keep track of all the points you are going to draw.
when you zoom in, it looks ugly
If you are wondering, what is the best way to draw a point, I would go with filled rectangle. You can see my jsperf here with comparison tests
In my Firefox this trick works:
function SetPixel(canvas, x, y)
{
canvas.beginPath();
canvas.moveTo(x, y);
canvas.lineTo(x+0.4, y+0.4);
canvas.stroke();
}
Small offset is not visible on screen, but forces rendering engine to actually draw a point.
The above claim that "If you are planning to draw a lot of pixel, it's a lot more efficient to use the image data of the canvas to do pixel drawing" seems to be quite wrong - at least with Chrome 31.0.1650.57 m or depending on your definition of "lot of pixel". I would have preferred to comment directly to the respective post - but unfortunately I don't have enough stackoverflow points yet:
I think that I am drawing "a lot of pixels" and therefore I first followed the respective advice for good measure I later changed my implementation to a simple ctx.fillRect(..) for each drawn point, see http://www.wothke.ch/webgl_orbittrap/Orbittrap.htm
Interestingly it turns out the silly ctx.fillRect() implementation in my example is actually at least twice as fast as the ImageData based double buffering approach.
At least for my scenario it seems that the built-in ctx.getImageData/ctx.putImageData is in fact unbelievably SLOW. (It would be interesting to know the percentage of pixels that need to be touched before an ImageData based approach might take the lead..)
Conclusion: If you need to optimize performance you have to profile YOUR code and act on YOUR findings..
This should do the job
//get a reference to the canvas
var ctx = $('#canvas')[0].getContext("2d");
//draw a dot
ctx.beginPath();
ctx.arc(20, 20, 10, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
Is there a way to apply a colorTransform to a BitmapData in a circle rather than in a rectangle?
Instead of erasing rectangular parts of an image by reducing the alpha channel as in the code below, I'd like to do it in circles.
_bitmap.colorTransform(new Rectangle(mouseX-d/2, mouseY-d/2, d, d),
new ColorTransform(1, 1, 1, .5, 0, 0, 0, 1));
I do have some code which loops through the pixels, extracts the alpha value and uses setPixel but it seams significantly slower than the colorTransform function.
Try creating a circle using the drawing API (flash.display.Graphics) and then drawing that onto the bitmap data with BlendMode.ERASE. That might solve your problem, if I understand it correctly.
var circle : Shape = new Shape;
circle.graphics.beginFill(0xffcc00, 1);
circle.graphics.drawEllipse(-50, -50, 100, 100);
// Create a transformation matrix for the draw() operation, with
// a translation matching the mouse position.
var mtx : Matrix = new Matrix();
mtx.translate(mouseX, mouseY);
// Draw circle at mouse position with the ERASE blend mode, to
// set affected pixels to alpha=0.
myBitmap.draw(circle, mtx, null, BlendMode.ERASE);
I'm not 100% sure that the ERASE blend mode works satisfyingly with the draw() command, but I can't see why it shouldn't. Please let me know how it works out!