I've got a canvas with the id=canvas and in this canvas are many balls. I need to add some information to these balls. So I've got a ball with the text="ball1" on it. And I need to add some text under this line, something like text2="My Super Ball".
Also I know the radius and coordinates of the balls.
How can I do so if I only got the canvas and ball id?
Problem: You can't change anything that's already been drawn on the html5 canvas
So...
You've been given a canvas with existing text-ball drawings and you need to change the text in one of the balls.
But...
Html5 canvas is like a real wall painting. You draw a red ball with "ball1" on the canvas and hang it on the wall. You can't later change the "ball1" text by replacing it with "My Super Ball".
Option#1: Redraw everything on the canvas
If the canvas contains only balls (==context.arc) filled with text (==context.fillText), then you can erase the entire canvas with context.clearRect and redraw the text-balls with your desired text.
Option#2: Redraw just the ball(s) that need text changes
If the canvas contains content that you don't want to destroy by clearing the canvas, then you can over-write a ball by refilling it with its original color and then drawing your changed text inside the refilled ball.
Here's example annotated code & a demo showing both methods:
// canvas vars
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// create an array of "ball" objects
// Each object contains sufficient info to redraw a ball
var balls=[
{x:100,y:100,radius:50,fill:'red',text:'ball1'},
{x:200,y:100,radius:35,fill:'blue',text:'ball2'},
{x:300,y:100,radius:25,fill:'green',text:'ball3'},
];
// To start, draw the basic balls
drawAll();
// listen for button clicks
$('#redrawAll').click(function(){
// toggle the super ball: ball1 <-> super
var b=balls[0];
if(b.text=='ball1'){
b.text='My Super Ball';
}else{
b.text='ball1';
}
// completely clear the canvas
ctx.clearRect(0,0,cw,ch);
// redraw all balls
drawAll();
});
//
$('#overwriteOne').click(function(){
// toggle the super ball: ball1 <-> super
var b=balls[0];
if(b.text=='ball1'){
b.text='My Super Ball';
}else{
b.text='ball1';
}
// over-write just the super ball
drawTextBall(b.x,b.y,b.radius,b.fill,b.text);
});
function drawAll(){
// completely clear the canvas
ctx.clearRect(0,0,cw,ch);
// redraw all balls from the info in balls[] array
for(var i=0;i<balls.length;i++){
var b=balls[i];
drawTextBall(b.x,b.y,b.radius,b.fill,b.text);
}
}
function drawTextBall(x,y,radius,fill,text){
// this function changes some styles so we'll be kind and
// restore the incoming style when we're done.
ctx.save();
// draw a red ball at x,y
ctx.beginPath();
ctx.arc(x,y,radius,0,Math.PI*2);
ctx.closePath();
ctx.fillStyle=fill;
ctx.fill();
// draw text centered at x,y
ctx.fillStyle='white';
ctx.textAlign='center';
ctx.textBaseline='middle';
ctx.fillText(text,x,y);
// restore the original incoming styles
ctx.restore();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id='redrawAll'>Clear and Redraw all balls</button>
<button id='overwriteOne'>Overwrite just 1 ball</button>
<br>
<canvas id="canvas" width=400 height=200></canvas>
Related
Ok I have an hard issue to solve. I have a HTML5 canvas in which I draw two charts (lines). I have the points of each chart where the lines are connected and I have two y-values (X,Y in the picture) where I have to draw a line and fill above or below the chart.
I really can't seem to get it work because I try coloring everything above the certain chart and clipping it with a rectangle but I have two chart so I must have two clipping areas which gives incorrect solution.
There is a picture attached to the post to see the case
So I have a red chart and a brown chart and values for X and Y (which are the colorful lines). X is the light blue - the height to where I want to color the graph below. Y is the light gray and is the height for coloring above the brown chart.
How can I implement this wihtout knowing the crossing point of the charts and X or Y?
The code I am using is this. I call it twice for every chart. I have omitted the code for drawing the chart - it is drawing using the "points" array.
unfortunately I don't have the points of the crossing between the end of the color area and the chart (the crossing of red and light blue ; brown and light gray)
ctx.rect(clipX, clipY, clipWidth, clipHeight);
ctx.stroke();
ctx.clip();
//fill the area of the chart above or below
ctx.globalAlpha = 0.4;
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y + visibleGraphicSpace);
for (var i = 1; i < points.length; i++) {
ctx.lineTo(points[i].x, points[i].y + visibleGraphicSpace);
}
ctx.closePath();
ctx.fill();
First I draw rectangule for the visible area, then I draw the chart with the given points array, close it and fill everything above or below till the end of the canvas. But this solution only takes the second filling right because it overrides the first one.
PS: I need to draw both coloring fillings not only one of them.
I hope I managed to explain it well enough. If you have any questions don't mind to ask.
Thank you for the help in advance.
You can create a clipping area (using context.clip) to make sure your blue and gray fills are contained inside the paths created by your chart. When you set a clipping area, any new drawings will not be displayed outside the clipping area.
Save some chart points in an array.
Save the top & bottom range of fill within the charted points
Define the chart path (==plot the points without stroking the points)
Create a clipping area from the path (all new drawing will be contained inside the clipping area and will not appear outside the area)
Fill the clipping area (with your blue & gray fills)
Stroke the path (with your red and maroon strokes)
Note: When you create a clipping path, it can only be "unclipped" by wrapping the clipping code inside context.save & context.restore.
Here's annotated code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var pts0=[
{x:119,y:239},
{x:279,y:89},
{x:519,y:249},
{x:739,y:83},
{x:795,y:163},
];
var fill0={top:75,bottom:133};
var pts1=[
{x:107,y:342},
{x:309,y:523},
{x:439,y:455},
{x:727,y:537},
{x:757,y:389}
];
var fill1={top:473,bottom:547};
filledChart(pts0,fill0,'red','skyblue');
filledChart(pts1,fill1,'maroon','lightgray');
function filledChart(pts,fill,strokecolor,fillcolor){
// define the path
// This doesn't stroke the path, it just "initializes" it for use
ctx.beginPath();
ctx.moveTo(pts[0].x,pts[0].y);
for(var i=0;i<pts.length;i++){
var pt=pts[i];
ctx.lineTo(pt.x,pt.y);
}
// save the un-clipped context state
ctx.save();
// Create a clipping area from the path
// All new drawing will be contained inside
// the clipping area
ctx.clip();
// fill some of the clipping area
ctx.fillStyle=fillcolor;
ctx.fillRect(0,fill.top,cw,fill.bottom-fill.top);
// restore the un-clipped context state
// (the clip is un-done)
ctx.restore();
// stroke the path
ctx.strokeStyle=strokecolor;
ctx.lineWidth=2;
ctx.stroke();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; }
<canvas id="canvas" width=800 height=550></canvas>
You could try to make the 'fill' before the chart.
You create the fill color.
You create the 'mask' (white color)
You create the chart line
An example, with only one chart, but can easily be changed to use two charts: https://jsfiddle.net/eLwc96fj/
var c2 = document.getElementById('test').getContext('2d');
// Create a colored rectangle
c2.fillStyle = '#0f0';
c2.rect(80,0, 200,70);
c2.fill();
// Create the 'mask' - it has the same path than the chart, but then follow the above rectangle.
c2.beginPath();
c2.fillStyle = '#fff';
c2.moveTo(80, 80);
c2.lineTo(120,50);
c2.lineTo(180, 90);
c2.lineTo(250, 40);
c2.lineTo(280, 120);
c2.lineTo(280, 0);
c2.lineTo(80, 0);
c2.closePath();
c2.fill();
// Draw the chart itself
c2.strokeStyle = '#f00';
c2.beginPath();
c2.moveTo(80, 80);
c2.lineTo(120,50);
c2.lineTo(180, 90);
c2.lineTo(250, 40);
c2.lineTo(280, 120);
c2.stroke();
Is it possible to skew an image on a canvas to make it appear as a reflection of another image?
Here is an illustration:
I need to flip and skew the image to get the desired effect, but I have not been able to get it to look like that. How can this be achieved?
I have found this example:
http://jsfiddle.net/jpnrzc9y/
ctx.save();
ctx.transform(1,0,0.3,-1,0,0);
ctx.drawImage(tree1,74,-117,20,40);
ctx.restore();
They seem to set the transform based on some random values.
But I cannot make sense of it. The values seem very random to me. Im trying to create a dynamic function that allows me to determine the amount of skew and that works for all images.
You can use context.scale to flip an image vertically:
// flip the canvas vertically
context.scale(1,-1);
Here are the steps to create a skewed reflection of an image:
Move (move==translate) the 0,0 origin to the desired x,y: ctx.translate(x,y);
Draw the original image image: ctx.drawImage(img,0,0);
Move to the bottom of the original image: ctx.translate(0,img.height);
Scale to vertically flip the image: ctx.scale(1,-1);
Apply a skew: ctx.transform(1,0,skewAngle,1,0,0);
Shrink the reflected image (just for aesthetics): ctx.scale(1,0.50);
Make the reflected image 50% opacity: ctx.globalAlpha=0.50;
Draw the reflected image: ctx.drawImage(img,0,-img.height);
Clean up by setting all transforms to default: ctx.setTransform(1,0,0,1,0,0);
Here's example code and a demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/character1.png";
function start(){
// 60x110
skewedReflection(img,25,25)
}
function skewedReflection(img,x,y){
// calc the 45 degree skew angle needed by ctx.transform
var skewAngle=-Math.tan(Math.PI/4);
// move the 0,0 origin to x,y
ctx.translate(x,y);
// draw the original image image
ctx.drawImage(img,0,0);
// move to the bottom of the original image
ctx.translate(0,img.height);
// use scale to flip the image
ctx.scale(1,-1);
// apply a skew
ctx.transform(1,0,skewAngle,1,0,0);
// shrink the reflected image
ctx.scale(1,0.50);
// make the reflected image 50% opacity
ctx.globalAlpha=0.50;
// draw the reflected image
ctx.drawImage(img,0,-img.height);
// clean up by setting all transforms to default
ctx.setTransform(1,0,0,1,0,0);
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=300 height=300></canvas>
I have a jpeg image that has been decoded, and I am writing it to canvas with putImageData. Is it possible to then move this image around? I cannot find any documentation on it.
The idea is that I will crop a certain part of the decoded image with dirtyX and dirtyY, and now I would like to crop another part of the image. I'm using the decoded image like a spritesheet.
Use the clipping version of drawImage to draw your individual sprites to the canvas
(Don't use getImageData & putImageData for clipping since drawImage is easier and faster.)
If you want to use your decoded image as a spritesheet then you can use the canvas's clipping version of context.drawImage to clip a particular sub-section of your spritesheet and draw it to the canvas.
Here's a previous SO post about the clipping version of drawImage.
HTML / Java Script Canvas - how to draw an image between source and destination points?
Here's example code and a Demo:
Note that the way to "move" sprites on the canvas is to clear the canvas and redraw the desired sprite in it's new position. (You can't "move" an existing drawing on the canvas).
$(window).load(function(){
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// animation related variables
var lastFlap,lastMove;
// define a bird object
// x,y are the position of the bird on the canvas
// spriteX,spriteY is the position of the first desired
// sprite image on the spritesheet
// width,height is the size of 1 sprite image
// currentFrame is the index of which of the sprite images to display
// currentDirection. The sprite plays forward and then backward to
// accomplish 1 flap. This determines if the next frame index will
// be increased (play forward) or decreased (play backward)
var bird={
x:30,
y:30,
spriteX:0,
spriteY:52,
width:51,
height:51,
frames:4,
currentFrame:0,
currentDirection:1
}
// load the spritesheet and start the animation
var spritesheet=new Image();
spritesheet.onload=start;
spritesheet.src="https://dl.dropboxusercontent.com/u/139992952/multple/birdSpritesheet.png";
function start(){
requestAnimationFrame(animate);
}
function animate(time){
// request another animation frame
if(bird.x<canvas.width){
requestAnimationFrame(animate);
}
// if the lastFlap or lastMove times don't aren't set, then set them
if(!lastFlap){lastFlap=time;}
if(!lastMove){lastMove=time;}
// calculate the elapsed times since the last flap and the last move
var elapsedFlap=time-lastFlap;
var elapsedMove=time-lastMove;
// if 50ms have elapsed, advance to the next image in this sprite
if(elapsedFlap>50){
// advance to next sprite on the spritesheet (flap)
bird.currentFrame+=bird.currentDirection;
// clamp bird.currentFrame between 0-3 (0,1,2,3)
// (because there are 4 images that make up the whole bird sprite)
if(bird.currentFrame<0 || bird.currentFrame>bird.frames-1){
bird.currentDirection*=-1;
bird.currentFrame+=bird.currentDirection;
}
// reset the flap timer
lastFlap=time;
}
// locate the current sprite from the spritesheet
var sx=bird.spriteX+bird.currentFrame*bird.width;
var sy=bird.spriteY;
// if 100ms have elapsed, move the bird across the canvas
if(elapsedMove>100){
bird.x+=3;
lastMove=time;
}
// clear the whole canvas
ctx.clearRect(0,0,canvas.width,canvas.height);
// draw the current part of the bird sprite at the current bird.x
ctx.drawImage(spritesheet,
sx,sy,bird.width,bird.height,
bird.x,bird.y,bird.width,bird.height
);
}
}); // end $(function(){});
body{ background-color: white; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>The canvas animating a clipped sprite</h4>
<canvas id="canvas" width=300 height=100></canvas>
<br>
<h4>The spritesheet</h4>
<img id=spritesheet src='https://dl.dropboxusercontent.com/u/139992952/multple/birdSpritesheet.png'>
Yes and No :-)
Since canvas draws in immediate-mode it doesn't know that you have drawn an image on the canvas. So, you cannot draw it on the canvas one single time and move the painted image around. You will have to manually redraw the complete image in each animationframe at a new postion.
You can do that using the requestAnimationFrame() as Jonas Grumann explains.
It's my first time to use html5 canvas and I have no idea yet how it works.
My problem is, I have to modify the colors of the images in the canvas. It's easy if there's only one image. But, I will have more than one, in other words, overlapping images.
To further understand my question, I created an illustration. There will be 2 image files only, Image 1 and Image 2:
This is my current code (with fiddle here too):
HTML:
<canvas id="canvas1" width="600" height="600"></canvas>
JS:
var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');
var ctx2 = can.getContext('2d');
ctx.fillStyle = 'yellow'; // background color. box in the middle is transparent. try changing this to see the effect
ctx.fillRect(40,0,250,300); // not sure if there's other way to fill in the tranparent area. but I created a box behind the image
var img = new Image();
img.onload = function() {
ctx.drawImage(img, 0, 0);
}
img.src = "http://s7.postimg.org/aruxhs8mz/pink.png"; //image 1
// I want to fill in the paw image too
/*ctx2.fillStyle = 'purple';
ctx2.fillRect(40,0,500,500); */
//should I declare something like this again?
var img2 = new Image();
img2.onload = function() {
ctx2.drawImage(img2, 0, 0);
}
img2.src = "http://s7.postimg.org/69smposl7/paw.png"; //image 2
//paw initially colored light blue. i would like to customize the color of this too
I should be able to fill the paw image in the middle as well and not just the main image. How to do that?
I have created a fiddle just to enlighten you with my question.
Hope someone can help me for any advice.
Thanks a bunch!
You can do your task with Compositing.
Compositing tells the canvas what to do when drawing additional new drawings (pixels) on the canvas.
In your case, 3 compositing modes are useful to learn.
Source-over Compositing
The default method of compositing is 'source-over' where new drawings are drawn over existing pixels.
// first draw a blue destination rectangle
ctx.fillStyle='blue';
ctx.fillRect(30,30,50,50);
// second draw a red source rectangle
ctx.fillStyle='red';
ctx.fillRect(60,60,50,50);
then results
Source-atop Compositing
'source-atop' compositing will draw new pixels only where the new pixels overlap the existing canvas pixels.
// first draw a blue destination rectangle
ctx.fillStyle='blue';
ctx.fillRect(30,30,50,50);
// set compositing to 'source-atop'
// (the new red pixels will only be drawn where
// they overlap the existing blue pixels)
ctx.globalCompositeOperation='source-atop';
// second draw a red source rectangle
// (red will overwrite only where it overlapped the blue)
ctx.fillStyle='red';
ctx.fillRect(60,60,50,50);
then results
Destination-over Compositing
'destination-over' compositing will draw new pixels under the existing canvas pixels.
// first draw a blue destination rectangle
ctx.fillStyle='blue';
ctx.fillRect(30,30,50,50);
// set compositing to 'source-atop'
// (the new red pixels will only be drawn where
// they overlap the existing blue pixels)
ctx.globalCompositeOperation='destination-over';
// second draw a red source rectangle
// (red will appear under the blue)
ctx.fillStyle='red';
ctx.fillRect(60,60,50,50);
then results
Here's how to use compositing to change the color of your paw.
Clear the canvas. You cannot directly change the color of anything previously drawn on the canvas, so the typical workflow of canvas is to erase it and redraw items in their new positions & colors.
Draw the paw image.
Set compositing to source-atop so new drawings will only be drawn where the existing paw pixels exist.
Fill the canvas with your new paw color using fillStyle & fillRect. This causes your paw to be recolored because the newly colored rectangle pixels will only appear where your paw pixels currently exist.
Set compositing to destination-over so new drawings will be drawn under existing pixels.
Fill the yellow box. Your paw will not be overwritten because new (yellow) pixels will be drawn "under" your paw.
Set compositing back to the default source-over so new drawings will be drawn on top of existing drawings.
Draw your frame that's transparent in the center. Your paw and the yellow background will show through the transparent center of your frame.
Here's example code and a Demo:
var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');
var ctx2 = can.getContext('2d');
var images=[];
var urls=[];
urls.push('http://s7.postimg.org/aruxhs8mz/pink.png');
urls.push('http://s7.postimg.org/69smposl7/paw.png');
var imgCount=urls.length;
document.getElementById('recolor').onclick=function(){
redrawWithNewPawColor();
};
for(var i=0;i<urls.length;i++){
images[i]=new Image();
images[i].onload=myOnload;
images[i].src=urls[i];
}
function myOnload(){
imgCount--;
if(imgCount>0){return;}
start();
}
function start(){
redrawWithNewPawColor()
}
function drawWithRecoloredPaw(newPawColor){
// clear the canvas
ctx.clearRect(0,0,can.width,can.height);
// draw the paw
ctx.drawImage(images[1], 0, 0);
// set compositing to source-atop
// so only existing pixels will be overwritten
ctx.globalCompositeOperation='source-atop';
// fill with new color
ctx.fillStyle=newPawColor;
// Because of compositing, only the paw is being color filled
ctx.fillRect(0,0,can.width,can.height);
// set compositing to destination-over
// so new pixels will be draw behind existing (paw) pixels
ctx.globalCompositeOperation='destination-over';
// change the fill color to yellow
ctx.fillStyle='yellow';
// fill the yellow box
ctx.fillRect(40,0,250,300);
// set compositing to the default of source-over
ctx.globalCompositeOperation='source-over';
// draw the transparent frame
ctx.drawImage(images[0],0,0);
}
function redrawWithNewPawColor(){
drawWithRecoloredPaw(randomColor());
}
function randomColor(){
return('#'+Math.floor(Math.random()*16777215).toString(16));
}
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<button id='recolor'>Recolor Paw</button>
<br>
<canvas id="canvas1" width=600 height=600></canvas>
the problem is that canvas does not allow you to adjust an image, but you could create a block over the paw wich has an opacity of 0.5 and than fill in the transparent part again, you could also use this:
http://www.w3schools.com/tags/canvas_globalcompositeoperation.asp
If the image is in your own web folder you can use it with an img tag and get the data an change it
much like the W3S example Changing image in canvas
after this step it's just a matter of redrawing the canvas with the new data
This will not work if the image comes from another source, becaus of security reasons
Let say I got the sprite to the left and I want to apply a red filter over the image before drawing it in a canvas so it looks like the sprite to the right. http://puu.sh/6dQ0E.png
Is there a way to do that?
It could be done in two steps, but I don't know if it's possible to draw the geometric figure that has the exact same shape than the image. (Ex: Drawing a rectangle over it won't match.)
Note: I want the filter to only be applied to this image, not the whole canvas.
You can combine 2 filtering operations to draw the red filter only on top of your existing sprite.
globalCompositeOperation="source-atop" will cause new drawings to only draw on top of existing non-transparent pixels.
globalAlpha will make your new drawings semi-transparent.
Taken together, you can draw a semi-transparent filter that only fills where your non-transparent ghost exsits.
Then just draw a red rectangle over your existing sprite (the rectangle will only be visible inside the existing ghost).
ctx.drawImage(ghostSprites,0,0);
ctx.globalAlpha=0.62;
ctx.globalCompositeOperation="source-atop";
ctx.fillStyle="red";
ctx.fillRect(spriteX,spriteY,spriteWidth,spriteHeight);
Demo: http://jsfiddle.net/m1erickson/W4XrG/
From here...
Notice the black outlines of your sprite become washed from the red filter.
You could also use context.getImageData to grab only the black pixels of your ghost. Then redraw those black pixels over your red-filtered ghost so the black outlines are not washed. If you feel ambitious, you could give that a try!
Good luck with your project!
Here’s code
<style>
body{ background-color: ivory; padding:20px;}
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var img=new Image();
img.onload=start;
img.crossOrigin="anonymous";
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/temp0a.png";
function start(){}
var spriteX=0;
var spriteY=0;
var spriteWidth=133;
var spriteHeight=161
$("#recolor").click(function(){
ctx.drawImage(img,0,0);
ctx.globalAlpha=0.62;
ctx.globalCompositeOperation="source-atop";
ctx.fillStyle="red";
ctx.fillRect(spriteX,spriteY,spriteWidth,spriteHeight);
});
}); // end $(function(){});
</script>
</head>
<body>
<p>Before</p>
<img src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/temp0a.png">
<p>After</p>
<button id="recolor">Click to recolor the green sprite</button><br>
<canvas id="canvas" width=300 height=161></canvas>
</body>
</html>