Related
Say we have a canvas:
<canvas id="one" width="100" height="200"></canvas>
var canvas = document.getElementById("one");
var context = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
// Sample graphic
context.beginPath();
context.rect(10, 10, 20, 50);
context.fillStyle = 'yellow';
context.fill();
context.lineWidth = 7;
context.strokeStyle = 'black';
context.stroke();
// create button
var button = document.getElementById("rotate");
button.onclick = function () {
// rotate the canvas 90 degrees each time the button is pressed
rotate();
}
var myImageData, rotating = false;
var rotate = function () {
if (!rotating) {
rotating = true;
// store current data to an image
myImageData = new Image();
myImageData.src = canvas.toDataURL();
myImageData.onload = function () {
// reset the canvas with new dimensions
canvas.width = ch;
canvas.height = cw;
cw = canvas.width;
ch = canvas.height;
context.save();
// translate and rotate
context.translate(cw, ch / cw);
context.rotate(Math.PI / 2);
// draw the previows image, now rotated
context.drawImage(myImageData, 0, 0);
context.restore();
// clear the temporary image
myImageData = null;
rotating = false;
}
}
}
And on a button click the canvas gets rotated -90 degrees anticlockwise (around the centre) and the dimensions of the canvas get also updated, so in a sense, it looks like this afterwards:
I want to rotate a canvas element to the anticlockwise rotation. I have used this code but it's not working as I want.
JavaScript has a built-in rotate() function for canvas context:
context.rotate( angle * Math.PI / 180);
The problem is that the rotation will only affect drawings made AFTER the rotation is done, which means you will need to:
Clear the canvas first: context.clearRect(0, 0, canvas.width, canvas.height);
Rotate the context context.rotate( 270 * Math.PI / 180);
Redraw the graphics
Thus, I recommend wrapping the graphics we want to draw in a function to make it easier to call after every rotation:
function drawGraphics() {
context.beginPath();
context.rect(10, 10, 20, 50);
context.fillStyle = 'yellow';
context.fill();
context.lineWidth = 7;
context.strokeStyle = 'black';
context.stroke();
}
the rotate() function seems to rotate the whole drawing area. Is there a way to rotate paths individually? I want the center for the rotation to be the object, not the drawing area.
Using save() and restore() still makes rotate take into account the whole drawing area.
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.save();
context.fillStyle = 'red';
context.rotate(0.35);
context.fillRect(40,40, 100, 100);
context.restore();
context.save();
context.fillStyle = 'blue';
context.rotate(0.35);
context.fillRect(200, 40, 100, 100);
context.restore();
<canvas id="canvas" width="500" height="500"></canvas>
Use local space
Instead of drawing object at the position you want them draw everything around its own origin in its local space. The origin is at (0,0) and is the location that the object rotates around.
So if you have a rectangle that you draw with
function drawRect(){
context.fillRect(200, 40, 100, 100);
}
change it so that it is drawn at its origin
function drawRect(){
context.fillRect(-50,-50 , 100, 100);
}
Now you can easily draw it wherevery you want
Start with the setTransform function as that clears any existing tranforms and is a convenient way to set the location of the center of the object will be
ctx.setTransform(1,0,0,1,posX,posY); // clear transform and set center location
if you want to rotate it then add the rotation
ctx.rotate(ang);
and scale with
ctx.scale(scale,scale);
if you have two different scales you should scale before the rotate.
Now just call the draw function
drawRect();
and it is drawn with its center at posX,posY rotated and scaled.
You can combine it all into a function that has the x,y position, the width and the height, scale and rotation. You can include the scale in the setTransform
function drawRect(x,y,w,h,scale,rotation){
ctx.setTransform(scale,0,0,scale,x,y);
ctx.rotate(rotation);
ctx.strokeRect(-w/2,-h/2,w,h);
}
It also applies to an image as a sprite, and I will include a alpha
function drawImage(img,x,y,w,h,scale,rotation,alpha){
ctx.globalAlpha = alpha;
ctx.setTransform(scale,0,0,scale,x,y);
ctx.rotate(rotation);
ctx.drawImage(img,-img.width/2,-img.height/2,img.width,img.height);
}
On a 6 year old laptop that can draw 2000 sprites on firefox every 1/60th of a second, each rotated, scaled, positioned, and with a alpha fade.
No need to mess about with translating back and forward. Just keep all the objects you draw around there own origins and move that origin via the transform.
Update
Lost the demo so here it is to show how to do it in practice.
Just draws a lot of rotated, scaled translated, alphaed rectangles.
By using setTransform you save a lot of time by avoiding save and restore
// create canvas and add resize
var canvas,ctx;
function createCanvas(){
canvas = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.left = "0px";
canvas.style.top = "0px";
canvas.style.zIndex = 1000;
document.body.appendChild(canvas);
}
function resizeCanvas(){
if(canvas === undefined){
createCanvas();
}
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
}
resizeCanvas();
window.addEventListener("resize",resizeCanvas);
// simple function to draw a rectangle
var drawRect = function(x,y,w,h,scale,rot,alpha,col){
ctx.setTransform(scale,0,0,scale,x,y);
ctx.rotate(rot);
ctx.globalAlpha = alpha;
ctx.strokeStyle = col;
ctx.strokeRect(-w/2,-h/2, w, h);
}
// create some rectangles in unit scale so that they can be scaled to fit
// what ever screen size this is in
var rects = [];
for(var i = 0; i < 200; i ++){
rects[i] = {
x : Math.random(),
y : Math.random(),
w : Math.random() * 0.1,
h : Math.random() * 0.1,
scale : 1,
rotate : 0,
dr : (Math.random() - 0.5)*0.1, // rotation rate
ds : Math.random()*0.01, // scale vary rate
da : Math.random()*0.01, // alpha vary rate
col : "hsl("+Math.floor(Math.random()*360)+",100%,50%)",
};
}
// draw everything once a frame
function update(time){
var w,h;
w = canvas.width; // get canvas size incase there has been a resize
h = canvas.height;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.clearRect(0,0,w,h); // clear the canvas
// update and draw each rect
for(var i = 0; i < rects.length; i ++){
var rec = rects[i];
rec.rotate += rec.dr;
drawRect(rec.x * w, rec.y * h, rec.w * w,rec.h * h,rec.scale + Math.sin(time * rec.ds) * 0.4,rec.rotate,Math.sin(time * rec.da) *0.5 + 0.5,rec.col);
}
requestAnimationFrame(update); // do it all again
}
requestAnimationFrame(update);
All transformations in canvas are for the whole drawing area. If you want to rotate around a point you're going to have to translate that point to the origin, do your rotation and translate it back. Something like this is what you want.
Use a rotate function to rotate all of the shape's points around its center.
<!DOCTYPE html>
<html>
<head>
<style>
body
{
margin: 0px;
padding: 0px;
overflow: hidden;
}
canvas
{
position: absolute;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
var canvas;
var context;
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var degreesToRadians = function(degrees)
{
return degrees*Math.PI/180;
}
var rotate = function(x, y, cx, cy, degrees)
{
var radians = degreesToRadians(degrees);
var cos = Math.cos(radians);
var sin = Math.sin(radians);
var nx = (cos * (x - cx)) + (sin * (y - cy)) + cx;
var ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
return new Vector2(nx, ny);
}
var Vector2 = function(x, y)
{
return {x:x,y:y};
}
var Shape = function(points, color)
{
this.color = color;
this.points = points;
};
Shape.prototype.rotate = function(degrees)
{
var center = this.getCenter();
for (var i = 0; i < this.points.length; i++)
{
this.points[i] = rotate(this.points[i].x,this.points[i].y,center.x,center.y,degrees);
}
context.beginPath();
context.arc(center.x,center.y,35,0,Math.PI*2);
context.closePath();
context.stroke();
}
Shape.prototype.draw = function()
{
context.fillStyle = this.color;
context.strokeStyle = "#000000";
context.beginPath();
context.moveTo(this.points[0].x, this.points[0].y);
for (var i = 0; i < this.points.length; i++)
{
context.lineTo(this.points[i].x, this.points[i].y);
//context.fillText(i+1, this.points[i].x, this.points[i].y);
}
context.closePath();
context.fill();
context.stroke();
}
Shape.prototype.getCenter = function()
{
var center = {x:0,y:0};
for (var i = 0; i < this.points.length; i++)
{
center.x += this.points[i].x;
center.y += this.points[i].y;
}
center.x /= this.points.length;
center.y /= this.points.length;
return center;
}
Shape.prototype.translate = function(x, y)
{
for (var i = 0; i < this.points.length; i++)
{
this.points[i].x += x;
this.points[i].y += y;
}
}
var Rect = function(x,y,w,h,c)
{
this.color = c;
this.points = [Vector2(x,y),Vector2(x+w,y),Vector2(x+w,y+h),Vector2(x,y+h)];
}
Rect.prototype = Shape.prototype;
var r = new Rect(50, 50, 200, 100, "#ff0000");
r.draw();
r.translate(300,0);
r.rotate(30);
r.draw();
</script>
</body>
</html>
I'm developing web app using canvas and I made three. canvas, canvas_panorama and canvas_image.
First one is something like main canvas, conteiner for the others. canvas_panorama is a background for canvas_image.
After canvas is right clicked, I'm computing angle to rotate canvas_image:
function getAngle( e, pw /*canvas*/ ){
var offset = pw.offset();
var center_x = (offset.left) + ($(pw).width() / 2);
var center_y = (offset.top) + ($(pw).height() / 2);
var mouse_x = e.pageX;
var mouse_y = e.pageY;
var radians = Math.atan2(mouse_x - center_x, mouse_y - center_y);
angle = radians;
}
After I have an angle I'm trying to rotate canvas_image like this:
function redraw(){
var p1 = ctx.transformedPoint(0,0);
var p2 = ctx.transformedPoint(canvas.width,canvas.height);
ctx.clearRect( p1.x, p1.y, p2.x-p1.x, p2.y-p1.y );
canvas_image_ctx.drawImage(image_img, 0, 0, 150, 150);
canvas_panorama_ctx.drawImage(panorama_img, 0, 0, 600, 300);
canvas_panorama_ctx.drawImage(canvas_image, 20, 20);
// rotate panorama_img around its center
// x = x + 0.5 * width
// y = y + 0.5 * height
canvas_panorama_ctx.translate(95, 95);
canvas_panorama_ctx.rotate(angle);
// translate to back
canvas_panorama_ctx.translate(-95, -95);
ctx.drawImage(canvas_panorama, 0, 0);
}
But this rotates both canvas_image and canvas_panorama. It should only rotate canvas_image
JSFiddle to show you my problem
I think you are confusing yourself with this idea of multiple canvases.
Once in the drawImage() method, every of your canvases are just images, and could be just one or even just plain shapes.
Transformation methods do apply to the canvas' context's matrix, and will have effect only if you do some drawing operations when they are set.
Note : To reset your context matrix, you can either use save(); and restore() methods which will also save all other properties of your context, so if you only need to reset the transform, then it's preferred to simply reset the transformation matrix to its default : ctx.setTransform(1,0,0,1,0,0).
Here is a simplified example to make things clearer :
var ctx = canvas.getContext('2d');
// a single shape, with the border of the context matrix
var drawRect = function(){
ctx.beginPath();
ctx.rect(10, 10, 50, 20);
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.stroke();
};
// set the color of our shapes
var gradient = ctx.createLinearGradient(0,0,70,0);
gradient.addColorStop(0,"green");
gradient.addColorStop(1,"yellow");
ctx.fillStyle = gradient;
// here comes the actual drawings
//we don't have modified the transform matrix yet
ctx.strokeStyle = "green";
drawRect();
// here we translate of 100px then we do rotate the context of 45deg
ctx.translate(100, 0)
ctx.rotate(Math.PI/4)
ctx.strokeStyle = "red";
drawRect();
// reset the matrix
ctx.setTransform(1,0,0,1,0,0);
// here we move of 150px to the right and 25px to the bottom
ctx.translate(150, 25)
ctx.strokeStyle = "blue";
drawRect();
// reset the matrix
ctx.setTransform(1,0,0,1,0,0);
<canvas id="canvas" width="500" height="200"></canvas>
In your code, you are setting the transformations on the canvas that does represent your image, and you do draw every of your canvases at each call.
What you want instead, is to set the transformation on the main canvas only, and draw the non-transformed image :
var main_ctx = canvas.getContext('2d');
var img_canvas = canvas.cloneNode();
var bg_canvas = canvas.cloneNode();
var angle = 0;
// draw on the main canvas, and only on the main canvas
var drawToMain = function(){
// first clear the canvas
main_ctx.clearRect(0,0,canvas.width, canvas.height);
// draw the background image
main_ctx.drawImage(bg_canvas, 0,0);
// do the transforms
main_ctx.translate(img_canvas.width/2, img_canvas.height/2);
main_ctx.rotate(angle);
main_ctx.translate(-img_canvas.width/2, -img_canvas.height/2);
// draw the img with the transforms applied
main_ctx.drawImage(img_canvas, 0,0);
// reset the transforms
main_ctx.setTransform(1,0,0,1,0,0);
};
// I changed the event to a simple onclick
canvas.onclick = function(e){
e.preventDefault();
angle+=Math.PI/8;
drawToMain();
}
// a dirty image loader
var init = function(){
var img = (this.src.indexOf('lena')>0);
var this_canvas = img ? img_canvas : bg_canvas;
this_canvas.width = this.width;
this_canvas.height = this.height;
this_canvas.getContext('2d').drawImage(this, 0,0);
if(!--toLoad){
drawToMain();
}
};
var toLoad = 2;
var img = new Image();
img.onload = init;
img.src = "http://pgmagick.readthedocs.org/en/latest/_images/lena_scale.jpg";
var bg = new Image();
bg.onload = init;
bg.src = 'http://www.fnordware.com/superpng/pnggradHDrgba.png';
<canvas id="canvas" width="500" height="300"></canvas>
This question already has an answer here:
How to fillstyle with Images in canvas html5
(1 answer)
Closed 7 years ago.
I'm trying to create a spinning wheel of sorts, where an image is displayed as a prize. I'm reusing a project I found online, and I'm pretty new to canvas, so I would appreciate some help.
This is how it looks, here an image would be displayed in each of the fields, with as angle to match the wheel. Here is the code generating it:
var outsideRadius = 210;
var textRadius = 160;
var insideRadius = 155;
ctx = canvas.getContext("2d");
ctx.clearRect(0,0,500,500);
ctx.strokeStyle = "#943127";
ctx.lineWidth = 4;
for(var i = 0; i < 12; i++) {
var angle = startAngle + i * arc;
ctx.fillStyle = '#a9382d';
ctx.beginPath();
ctx.arc(250, 250, outsideRadius, angle, angle + arc, false);
ctx.arc(250, 250, insideRadius, angle + arc, angle, true);
ctx.closePath();
ctx.stroke();
ctx.fill();
}
In each of the fields above should be displayed a image of a prize from an array. Im having problems drawing the images in the fields. I've tried using createPattern() without luck.
EDIT: Added jsfiddle: http://jsfiddle.net/46k72m7z/
To clip an image inside one of your wheel-wedges:
See illustration below.
Calculate the 4 vertices of your specified wedge.
Begin a new Path with context.beginPath and move to point0.
Draw a line from point0 to point1.
Draw an arc from point1 to point2.
Draw a line from point2 to point3.
Draw an arc from point3 back to point0.
Close the path (not needed, just being extra careful).
Call context.clip() to create a clipping area of your wedge.
Drawing the image at the appropriate angle
See illustration below.
Find the centerpoint of the wedge.
Set the canvas origin to that centerpoint with context.translate.
Calculate the angle from the wheel center (point#1 below) to the wedge center.
Rotate the canvas by the calculated angle.
Draw the image offset by half the image's width & height. This causes the image to be visually centered inside the wedge.
Other useful information
When you set a clip with context.clip the only way to undo that clip is to wrap the clip between context.save and context.restore. (Or resize the canvas, but that's counter-productive when you're trying to draw multiple clipped regions because all content is erased when the canvas is resized).
// save the begininning unclipped context state
context.save();
... draw your path
// create the clipping area
context.clip();
... draw the image inside the clipping area
// restore the context to its unclipped state
context.restore();
To center an image anywhere, find the center point where you want the image centered and then draw the image offset by half it's width & height:
ctx.drawImage( img,-img.width/2, -img.height/2 );
Calculating points on the circumference of a circle:
// given...
var centerX=150; // the circle's center
var centerY=150;
var radius=25; // the circle's radius
var angle=PI/2; // the desired angle on the circle (angles are expressed in radians)
var x = centerX + radius * Math.cos(angle);
var y = centerY + radius * Math.sin(angle);
An efficiency: The path command will automatically draw a line from the previous command's endpoint to your new command's startpoint. Therefore, you can skip step#3 & step#5 because the lines will be drawn automatically when you draw the arcs.
Example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
var PI=Math.PI;
var cx=250;
var cy=250;
var outsideRadius = 210;
var textRadius = 160;
var insideRadius = 155;
var wedgecount=12;
var arc=Math.PI*2/wedgecount;
var startAngle=-arc/2-PI/2;
var mm=new Image;
mm.onload=start;
mm.src='https://dl.dropboxusercontent.com/u/139992952/multple/mm1.jpg';
var goldCar=new Image();
goldCar.onload=start;
goldCar.src='https://dl.dropboxusercontent.com/u/139992952/multple/carGold.png';
var redCar=new Image();
redCar.onload=start;
redCar.src='https://dl.dropboxusercontent.com/u/139992952/multple/cars1.png';
var imgCount=2;
function start(){
if(++imgCount<2){return;}
drawWheel();
for(var i=0;i<wedgecount;i++){
var img=(i%2==0)?goldCar:redCar;
if(i==3){img=mm;}
clipImageToWedge(i,img);
}
}
function drawWheel(){
ctx.clearRect(0,0,500,500);
ctx.lineWidth = 4;
for(var i = 0; i < 12; i++) {
var angle = startAngle + i * arc;
ctx.fillStyle = '#a9382d';
ctx.beginPath();
ctx.arc(cx,cy, outsideRadius, angle, angle + arc, false);
ctx.arc(cx,cy, insideRadius, angle + arc, angle, true);
ctx.closePath();
ctx.stroke();
ctx.fill();
}
}
function clipImageToWedge(index,img){
var angle = startAngle+arc*index;
var angle1= startAngle+arc*(index+1);
var x0=cx+insideRadius*Math.cos(angle);
var y0=cy+insideRadius*Math.sin(angle);
var x1=cx+outsideRadius*Math.cos(angle);
var y1=cy+outsideRadius*Math.sin(angle);
var x3=cx+outsideRadius*Math.cos(angle1);
var y3=cy+outsideRadius*Math.sin(angle1);
ctx.save();
ctx.beginPath();
ctx.moveTo(x0,y0);
ctx.lineTo(x1,y1);
ctx.arc(cx,cy,outsideRadius,angle,angle1);
ctx.arc(cx,cy,insideRadius,angle1,angle,true);
ctx.clip();
var midRadius=(insideRadius+outsideRadius)/2;
var midAngle=(angle+angle1)/2;
var midX=cx+midRadius*Math.cos(midAngle);
var midY=cy+midRadius*Math.sin(midAngle);
ctx.translate(midX,midY);
ctx.rotate(midAngle+PI/2);
ctx.drawImage(img,-img.width/2,-img.height/2);
ctx.restore();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=500 height=500></canvas>
I have code like:
<body>
<canvas id="main" width=400 height=300></canvas>
<script>
var windowToCanvas = function(canvas, x, y) {
var bbox = canvas.getBoundingClientRect();
return {
x: (x - bbox.left) * (canvas.width / bbox.width),
y: (y - bbox.top) * (canvas.height / bbox.height)
};
};
image = new Image();
image.src = "redball.png";
image.onload = function (e) {
var canvas = document.getElementById('main'),
context = canvas.getContext('2d');
var pattern = context.createPattern(image, "repeat");
function draw(loc) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = pattern;
context.beginPath();
context.moveTo(loc.x, loc.y);
context.lineTo(loc.x + 300, loc.y + 60);
context.lineTo(loc.x + 70, loc.y + 200);
context.closePath();
context.fill();
}
canvas.onmousemove = function(e) {
var event = e || window.event,
x = event.x || event.clientX,
y = event.y || event.clientY,
loc = windowToCanvas(canvas, x, y);
draw(loc);
};
}
</script>
</body>
I call createPattern API, use a background image to fill a Triangle, but when mouse move, the background image also move, I only want the background image at fixed position, how Can I fix this?
Think of a context pattern as a background image on the canvas.
Patterns always begin at the canvas origin [0,0]. If the pattern repeats, then the pattern fills the canvas in tiles repeating rightward and downward.
Therefore, your triangle will always reveal a different portion of the pattern if you move the triangle around the canvas.
There are multiple ways of having your triangle always reveal the same portion of the pattern image.
Option#1 -- context.translate
Move the canvas origin from its default [0,0] position to your triangle position [loc.x,loc.y]. You can do this with canvas transformations. In particular, the translate command will move the origin. Moving the origin will also move the top-left starting position of your pattern so that the pattern always aligns the same way relative to your moving triangle:
var pattern = context.createPattern(image, "repeat");
context.fillStyle=pattern;
function draw(loc) {
context.clearRect(0, 0, canvas.width, canvas.height);
// the origin [0,0] is now [loc.x,loc.y]
context.translate(loc.x,loc.y);
context.beginPath();
// you are already located at [loc.x,loc.y] so
// you don't need to add loc.x & loc.y to
// your drawing coordinates
context.moveTo(0,0);
context.lineTo(300,60);
context.lineTo(70,200);
context.closePath();
context.fill();
// always clean up! Move the origina back to [0,0]
context.translate(-loc.x,-loc.y);
}
A Demo using translate:
var windowToCanvas = function(canvas, x, y) {
var bbox = canvas.getBoundingClientRect();
return {
x: (x - bbox.left) * (canvas.width / bbox.width),
y: (y - bbox.top) * (canvas.height / bbox.height)
};
};
image = new Image();
image.src = "https://dl.dropboxusercontent.com/u/139992952/multple/jellybeans.jpg";
image.onload = function (e) {
var canvas = document.getElementById('main'),
context = canvas.getContext('2d');
var pattern = context.createPattern(image, "repeat");
context.fillStyle=pattern;
draw({x:0,y:0});
function draw(loc) {
context.clearRect(0, 0, canvas.width, canvas.height);
// the origin [0,0] is now [loc.x,loc.y]
context.translate(loc.x,loc.y);
context.beginPath();
// you are already located at [loc.x,loc.y] so
// you don't need to add loc.x & loc.y to
// your drawing coordinates
context.moveTo(0,0);
context.lineTo(300,60);
context.lineTo(70,200);
context.closePath();
context.fill();
// always clean up! Move the origina back to [0,0]
context.translate(-loc.x,-loc.y);
}
canvas.onmousemove = function(e) {
var event = e || window.event,
x = event.x || event.clientX,
y = event.y || event.clientY,
loc = windowToCanvas(canvas, x, y);
draw(loc);
};
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<h4>Move the mouse to move the triangle<br>The image is large, so be patient while it loads</h4>
<canvas id="main" width=600 height=600></canvas>
Option#2 -- compositing
Use compositing instead of patterning to draw an image atop your triangle. Compositing is a method to control how new pixels to be drawn on the canvas will interact with already existing canvas pixels. In particular, source-atop compositing will cause any new pixels to only be drawn where the new pixel overlaps an existing non-transparent pixel. What you would do is draw your triangle in a solid color and then use source-atop compositing to draw your image only where the solid triangle pixels are:
function draw(loc) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.moveTo(loc.x, loc.y);
context.lineTo(loc.x + 300, loc.y + 60);
context.lineTo(loc.x + 70, loc.y + 200);
context.closePath();
// fill the triangle with a solid color
context.fill();
// set compositing to 'source-atop' so
// new drawings will only be visible if
// they overlap a solid color pixel
context.globalCompositeOperation='source-atop';
context.drawImage(image,loc.x,loc.y);
// always clean up! Set compositing back to its default value
context.globalCompositeOperation='source-over';
}
A Demo using compositing:
var windowToCanvas = function(canvas, x, y) {
var bbox = canvas.getBoundingClientRect();
return {
x: (x - bbox.left) * (canvas.width / bbox.width),
y: (y - bbox.top) * (canvas.height / bbox.height)
};
};
image = new Image();
image.src = "https://dl.dropboxusercontent.com/u/139992952/multple/jellybeans.jpg";
image.onload = function (e) {
var canvas = document.getElementById('main'),
context = canvas.getContext('2d');
var pattern = context.createPattern(image, "repeat");
context.fillStyle=pattern;
draw({x:0,y:0});
function draw(loc) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.moveTo(loc.x, loc.y);
context.lineTo(loc.x + 300, loc.y + 60);
context.lineTo(loc.x + 70, loc.y + 200);
context.closePath();
// fill the triangle with a solid color
context.fill();
// set compositing to 'source-atop' so
// new drawings will only be visible if
// they overlap a solid color pixel
context.globalCompositeOperation='source-atop';
context.drawImage(image,loc.x,loc.y);
// always clean up! Set compositing back to its default value
context.globalCompositeOperation='source-over';
}
canvas.onmousemove = function(e) {
var event = e || window.event,
x = event.x || event.clientX,
y = event.y || event.clientY,
loc = windowToCanvas(canvas, x, y);
draw(loc);
};
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<h4>Move the mouse to move the triangle<br>The image is large, so be patient while it loads</h4>
<canvas id="main" width=600 height=600></canvas>
More Options...
There are more possible options, too. I'll mention some of them without giving code examples:
Create an img element of your filled triangle and use drawImage(img,loc.x,loc.y) to move that triangle-image around the canvas
Create a clipping region from your triangle. Clipping regions cause new drawings to only be displayed in the defined clipping region. In this case, the new drawImage would only be visible inside your triangle shape and would not be visible outside your triangle.
And more options that are less conventional...