Add text to custom shape without Kinetic.Group - javascript

I am in the process of updating to kineticjs 4.7.0. I am struggling with adding text to a custom shape.
Here the code:
var triangle = new Kinetic.Shape({
drawFunc: function(context) {
this.setFill('#00D2FF');
context.beginPath();
context.moveTo(200, 50);
context.lineTo(420, 80);
context.quadraticCurveTo(300, 100, 260, 170);
context.closePath();
context.fillStrokeShape(this);
this.setFill('#FFFFFF');
context.beginPath();
context.fillText('Hello World!', 200, 150);
context.closePath();
context.fillStrokeShape(this);
},
stroke: 'black',
strokeWidth: 4
});
How do I make the text a different color to the filling of the shape, so I don't need to use Kinetic.Shape and Kinetic.Text in a group?
Here the is jsfiddle http://jsfiddle.net/qQU6G/1/

Yes, it appears that KineticJS 4.7 has a more complete wrapper of canvas.context, but the fillText method does not yet respect either context.fillStyle or this.setFill.
[ Update ]
Since Kinetic's "context" is not full-featured in regards to fillText, here's a way to get the underlying context and use that to fillText with your different color.
Here's a Fiddle example: http://jsfiddle.net/m1erickson/df6Uv/
var triangle = new Kinetic.Shape({
drawFunc: function(context) {
this.setFill('#00D2FF');
context.beginPath();
context.moveTo(200, 50);
context.lineTo(420, 80);
context.quadraticCurveTo(300, 100, 260, 170);
context.closePath();
context.fillStrokeShape(this);
var ctx=this.getContext()._context;
ctx.save();
ctx.font="18px verdana";
ctx.fillStyle="#ffffff";
ctx.fillText("Hello World!",225,90);
ctx.restore();
},
stroke: 'black',
strokeWidth: 4
});

Be warned that drawFunc can be invoked more than once and not always you will get context from canvas you expect. Kinetic will use helper canvases sometimes. I run into problems using above approach, but wrapping "extra" code with simple check helped:
...
if(context.canvas._canvas.parentNode!=null){
var ctx=this.getContext()._context;
ctx.save();
ctx.font="18px verdana";
ctx.fillStyle="#ffffff";
ctx.fillText("Hello World!",225,90);
ctx.restore();
}
...

Related

HTML Canvas globalCompositeOperation without affecting previous layers

I have these 4 layers.
What I'm trying to do is put the red and blue layer into one mask. But I don't want the purple or orange layer to be affected by this mask (only the red and blue). I manage to make it work for the orange but not for the purple layer
See my code
var canvas = document.querySelector('canvas');
canvas.height = window.innerHeight
canvas.width = window.innerWidth
var ctx = canvas.getContext('2d');
//this should'nt be affected by the mask
ctx.beginPath()
ctx.fillStyle = 'purple';
ctx.rect(0, 50, 100, 100);
ctx.fill()
//this is the mask
ctx.beginPath()
ctx.rect(10, 10, 70, 70);
ctx.fillStyle = 'green';
ctx.fill()
ctx.globalCompositeOperation = 'source-atop';
//this need to be inside the mask
ctx.beginPath()
ctx.fillStyle = 'blue';
ctx.rect(10, 10, 100, 100);
ctx.fill()
//this need to be inside the mask
ctx.beginPath()
ctx.fillStyle = 'red';
ctx.rect(50, 40, 100, 100);
ctx.fill()
ctx.globalCompositeOperation = 'source-over'; //reset
//this should'nt be affected by the mask
ctx.beginPath()
ctx.fillStyle = 'orange';
ctx.rect(200, 40, 100, 100);
ctx.fill()
And the fiddle https://jsfiddle.net/ws3b4q95/4/
Canvas doesn't know about shapes as objects, it only cares about pixels. So the purple rectangle can't be excluded from your mask, because everyting that's already drawn on the canvas, will be part of the mask.
Instead you should draw the rectangle after you've applied the mask, and use destination-over operation:
//this need to be inside the mask
ctx.beginPath()
ctx.fillStyle = 'red';
ctx.rect(50, 40, 100, 100);
ctx.fill()
//this should'nt be affected by the mask
ctx.globalCompositeOperation = 'destination-over';
ctx.beginPath()
ctx.fillStyle = 'purple';
ctx.rect(0, 40, 100, 100);
ctx.fill()
This is nice summary from Mozilla about composite operations: MDN web docs: CanvasRenderingContext2D.global .CompositeOperation

layering and outlining on HTML canvas

I have been working on a seemingly simple graphic. I wish to create circles, with a line connecting the circles, and filling the circles in with some background. I have almost got it, but this one piece is tripping me up.
I can define the canvas, create the circles, and line connecting them just fine:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = $(window).width();
canvas.height = $(window).height();
ctx.beginPath();
ctx.strokeStyle = "black";
ctx.lineWidth = 10;
//Create two nodes
ctx.arc( 100, 100, 25, 0, 2*Math.PI);
ctx.moveTo(200+25, 200)
ctx.arc( 200, 200, 25, 0, 2*Math.PI);
//line connecting two nodes
ctx.moveTo(100, 100);
ctx.lineTo(200, 200);
ctx.stroke();
This would look like this:
What I then do is fill the circles with an image (this is why I use clip()), but using a white color fill for the sake of example demonstrates the problem as well:
//simulate filling in nodes with image, in this case solid color
ctx.clip();
ctx.fillStyle = "white";
ctx.fill();
Now I am almost there, but there are some jagged edges there that I have read is just a little "bug" in Chrome, and also I like that thick black outline on the circles. So, I want to go back over just the 2 circles and outline them. It seems no matter what I do, the context always remembers that line connecting the two, and I end up with the connector line over the top of the image after calling stroke():
//would like to just re-outline circles, not connecting line
ctx.stokeStyle = "black";
ctx.arc( 100, 100, 25, 0, 2*Math.PI);
ctx.moveTo(200+25, 200)
ctx.arc( 200, 200, 25, 0, 2*Math.PI);
ctx.stroke();
What I can't figure out is how to just outline the 2 circles again after filling in the white background (loading the image)?
I think about it like drawing in layers. First I draw some lines, then I put the images in, then I draw again on top. Not sure if the html canvas is meant to be used like that. Thanks.
JSFiddle Example Here
You are forgetting to begin a new path.
Whenever you start a new shape you must use ctx.beginPath or the context will redraw all the previous paths.
BTW the jaggy circles is because you are re-rendering them, this causes the edges to get jaggies.
var canvas = document.createElement("canvas");
var ctx = canvas.getContext('2d');
canvas.width = 500;
canvas.height = 500;
document.body.appendChild(canvas);
ctx.setTransform(1,0,0,1,0,-50); // just moving everything up to be seen in snippet.
ctx.beginPath();
ctx.strokeStyle = "black";
ctx.fillStyle = "#FAFAFF";
ctx.lineWidth = 10;
//Create two nodes
/* dont draw the two circle the first time as you are
doubling the render causing the edges to get to sharp
making them appear jaggy.
ctx.arc( 100, 100, 25, 0, 2*Math.PI);
ctx.moveTo(200+25, 200)
ctx.arc( 200, 200, 25, 0, 2*Math.PI);
*/
//line connecting two nodes
ctx.moveTo(100, 100);
ctx.lineTo(200, 200);
ctx.stroke();
ctx.beginPath(); // start a new path and removes all the previous paths
//Create two nodes
ctx.arc( 100, 100, 25, 0, 2*Math.PI);
ctx.moveTo(200+25, 200)
ctx.arc( 200, 200, 25, 0, 2*Math.PI);
ctx.fill();
ctx.beginPath(); // start a new path and removes all the previous paths
//Create two nodes
ctx.arc( 100, 100, 25, 0, 2*Math.PI);
ctx.moveTo(200+25, 200)
ctx.arc( 200, 200, 25, 0, 2*Math.PI);
ctx.stroke();

Different methods of using drawing rectangle

Trying to learn javascript canvas, but having a difficult time figuring out which of these is preferred:
<script>
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.rect(10, 10, 100, 100);
context.rect(50, 50, 100, 100);
context.stroke();
</script>
or
<script>
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.beginPath();
context.rect(10, 10, 100, 100);
context.rect(50, 50, 100, 100);
context.stroke();
</script>
or
<script>
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.beginPath();
context.rect(10, 10, 100, 100);
context.stroke();
context.beginPath();
context.rect(50, 50, 100, 100);
context.stroke();
</script>
It seems to not matter which one I choose, two triangles will be drawn anyhoo. It is said that everytime beginPath() is called, the previous subpath made gets erased. But what about when to beginPaths are used, like in the last code snippet. Doesn't the last subpath need to be erased?
I guess that the concept of paths seems lost on me; everywhere I read that it's "like drawing with a pencil and then inking the lines". Great! But none seem to explain why this is. There's a strokeRect, why not just have a line that gets drawn immediately? Does it have to do with optimalization?
beginPath resets the current path in the context, if you've made any settings/adding paths in the context, beginPath will reset them. In this example:
context.beginPath();
context.rect(10, 10, 100, 100);
context.beginPath(); // Resets path
context.rect(50, 50, 100, 100);
context.stroke();
since you reset the path without calling stroke, you'll only get one rectangle. It has nothing to do with clearing the screen, just clearing whatever is in the memory of the context, so to speak.
http://jsbin.com/getejuxeva/edit
If you don't quite get what it's good for, compare these two:
http://jsbin.com/kojojofixa/1/edit
and
http://jsbin.com/gevacefumo/1/edit

How to scale a canvas image larger and smaller in a continuous loop?

I created a cloud shape in canvas, and I'm wondering how I can scale the shape back and forth between larger and smaller. Like I want the cloud to get bigger, than smaller, then bigger then smaller, etc.
I was able to be able to move a separate canvas image up and down using a when-then method, but I don't think that method will work by increasing the canvas size because the actual image stays to scale.
Here is my canvas code:
<script>
var canvas = document.getElementById('hardware-cloud');
var context = canvas.getContext('2d');
// begin cloud shape-Hardware
context.beginPath();
context.moveTo(180, 80);
context.bezierCurveTo(150, 132, 143, 165, 203, 154);
context.bezierCurveTo(203, 154, 180, 200, 260, 175);
context.bezierCurveTo(297, 231, 352, 198, 344, 185);
context.bezierCurveTo(344, 185, 372, 215, 374, 175);
context.bezierCurveTo(473, 165, 462, 132, 429, 110);
context.bezierCurveTo(473, 44, 407, 33, 374, 55);
context.bezierCurveTo(352, 10, 275, 22, 275, 55);
context.bezierCurveTo(210, 20, 165, 22, 180, 80);
// complete cloud shape-Hardware
context.closePath();
context.lineWidth = 5;
context.strokeStyle = 'navy';
context.stroke();
context.fillStyle = 'white';
context.fill();
//font inside hardware cloud
context.beginPath();
context.font = 'bold 15pt Calibri';
context.textAlign = 'center';
context.fillStyle ="navy"; // <-- Text colour here
context.fillText('Why not the electronic store?', 300, 120);
context.lineWidth = 2;
context.strokeStyle = 'grey';
context.stroke();
context.closePath();
//top hardware circle
context.beginPath();
context.arc(380, 220, 13, 0, Math.PI * 2, false);
context.lineWidth = 5;
context.strokeStyle = 'navy';
context.stroke();
context.fillStyle = 'white';
context.fill();
context.closePath();
//middle hardware circle
context.beginPath();
context.arc(398, 253, 10, 0, Math.PI * 2, false);
context.lineWidth = 5;
context.strokeStyle = 'navy';
context.stroke();
context.fillStyle = 'white';
context.fill();
context.closePath();
//bottom hardware circle
context.beginPath();
context.arc(425, 273, 7, 0, Math.PI * 2, false);
context.lineWidth = 5;
context.strokeStyle = 'navy';
context.stroke();
context.fillStyle = 'white';
context.fill();
context.closePath();
</script>
And here is my attempt at the jQuery. The first part of it is to get the div region to slide into view. The second part is an attempt to scale up and down.:
$(document).ready(function(){
$('#hardware').hide();
$('#hardware').show("slide", {direction: "left"}, 400 );
ani();
function ani(){
$.when(
$('#hardware-cloud').effect({
scale: "120"},700),
$('#hardware-cloud').effect({
scale: "100"},700)
.then(ani));}
});
You can use scale and translate to change the size of the shape.
All transforms works for the next drawn shape and doesn't affect already drawn shapes.
So for it to work you'll need to clear the canvas, transform and then redraw the shape.
For example, re-factor the code so that you can call shape() to draw the cloud, then:
Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height);
Apply scale ctx.scale(scaleX, scaleY);
Optionally translate the shape using ctx.translate(deltaX, deltaY);
Redraw shape shape();
Repeat using for example requestAnimationFrame() in a loop.
Scale value is 1 = 1:1, 0.5 = half etc. Just remember these transforms are accumulative (you can use setTransform() to set absolute transforms each time).
Update
Here is one way you can do this:
var maxScale = 1, // for demo, this represents "max"
current = 0, // angle (in radians) used to scale smoother
step = 0.02, // speed
pi2 = Math.PI; // cached value
// main loop clears, transforms and redraws shape
function loop() {
context.clearRect(0, 0, canvas.width, canvas.height);
transform();
drawShape();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
// set scale based on rotation
function transform() {
current += step;
current %= pi2;
// just play around with different combinations here
var s = (maxScale * Math.abs(Math.sin(current))) / maxScale + 0.5;
// set absolute scale
context.setTransform(s, 0, 0, s, 0, 0);
}
// wrap shape calls in a function so it can be reused
function drawShape() {
// begin cloud shape-Hardware
context.beginPath();
... rest goes here...
Online demo
In addition you can use translate() to re-position the shape linked to the rotation value.
Hope this helps!

How can you fill a shape created in javascript with an image?

How can you fill a shape created in javascript with an image?
I am using a shape I created with javascript and am right now filling it with a color. How can I replace that and fill it with an image/gif of my choice?
function shape(x,y) {
ctx.fillStyle = "#9dd4ff";
ctx.beginPath();
ctx.moveTo(232,213)
ctx.lineTo(315,198);
ctx.lineTo(x,y);
ctx.closePath();
ctx.fill();
}
You can solve this problem by using your path as a MASK
HERE IS THE CODE
// Create an image element
var img = document.createElement('IMG');
// When the image is loaded, draw it
img.onload = function () {
// Save the state, so we can undo the clipping
ctx.save();
// Create a shape, of some sort
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(100, 30);
ctx.lineTo(180, 10);
ctx.lineTo(200, 60);
ctx.arcTo(180, 70, 120, 0, 10);
ctx.lineTo(200, 180);
ctx.lineTo(100, 150);
ctx.lineTo(70, 180);
ctx.lineTo(20, 130);
ctx.lineTo(50, 70);
ctx.closePath();
// Clip to the current path
ctx.clip();
ctx.drawImage(img, 0, 0);
// Undo the clipping
ctx.restore();
}
// Specify the src to load the image
img.src = "https://www.google.com/images/srpr/logo3w.png";
Try this:
var thumbImg = document.createElement('img');
thumbImg.src = 'path_to_image';
function shape(x,y) {
ctx.fillStyle = "#9dd4ff";
ctx.beginPath();
ctx.moveTo(232,213)
ctx.lineTo(315,198);
ctx.lineTo(x,y);
ctx.drawImage(thumbImg, 0, 0, 50, 50);
ctx.closePath();
ctx.fill();
}
-- Edit --
Remove ctx.fillStyle ="#9dd4ff"
As you need to mention the path of image.

Categories