Canvas createPattern() and fill() with an offset - javascript

I'm using the canvas to draw simple rectangles and fill them with a flooring pattern (PNG). But if I utilize a "Camera" script to handle transform offsets for the HTML5 canvas, the rectangle shape moves appropriately, but the fill pattern seems to always draw from a fixed point on the screen (I'm assuming the top left). Is there a way to "nail down" the fill pattern so it always lines up with the rectangle, no matter the canvas transform, or a way to add an offset on fill() that can be calculated elsewhere in the code? I could just use drawImage() of course, but drawing the rectangles is more versatile for my purposes.
sampleDrawFunction(fillTexture, x1, y1, x2, y2) {
// This is oversimplified, but best I can do with a ~10k lines of code
x1 -= Camera.posX;
x2 -= Camera.posX;
y1 = -1 * y1 - Camera.posY;
y2 = -1 * y2 - Camera.posY;
// Coordinates need to be adjusted for where the camera is positioned
var img = new Image();
img.src = fillTexture;
var pattern = this.ctx.createPattern(img, "repeat");
this.ctx.fillStyle = pattern;
// Translate canvas's coordinate pattern to match what the camera sees
this.ctx.save();
this.ctx.translate(Camera.posX - Camera.topLeftX, Camera.posY - Camera.topLeftY);
this.ctx.fillStyle = pattern;
this.ctx.fillRect(x1, y1, x2 - x1, y2 - y1);
this.ctx.restore();
}
Thank you.

Canvas fills (CanvasPatterns and CanvasGradients) are always relative to the context's transformation matrix, so they do indeed default at top left corner of the canvas, and don't really care about where the path using them will be be.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = begin; //!\ ALWAYS WAIT FOR YOUR IMAGE TO LOAD BEFORE DOING ANYTHING WITH IT!
img.crossOrigin = "anonymous";
img.src = "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png";
function begin() {
const rect_size = 20;
ctx.fillStyle = ctx.createPattern( img, 'no-repeat' );
// drawing a checkerboard of several rects shows that the pattern doesn't move
for ( let y = 0; y < canvas.height; y += rect_size ) {
for ( let x = (y / rect_size % 2) ? rect_size : 0 ; x < canvas.width; x += rect_size * 2 ) {
ctx.fillRect( x, y, rect_size, rect_size );
}
}
}
<canvas id="canvas" width="500" height="500"></canvas>
Now, because they are relative to the context transform matrix, this means we can also move them by changing that transformation matrix:
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = begin; //!\ ALWAYS WAIT FOR YOUR IMAGE TO LOAD BEFORE DOING ANYTHING WITH IT!
img.crossOrigin = "anonymous";
img.src = "https://upload.wikimedia.org/wikipedia/commons/f/f7/Cool_bunny_sprite.png";
function begin() {
const rect_size = 244;
const max_speed = 5;
let x_speed = Math.random() * max_speed;
let y_speed = Math.random() * max_speed;
ctx.fillStyle = ctx.createPattern( img, 'repeat' );
let x = 0;
let y = 0;
requestAnimationFrame( anim );
function anim( now ) {
clear();
x = (x + x_speed);
y = (y + y_speed);
if( x > canvas.width || x < -rect_size ) {
x_speed = Math.random() * max_speed * -Math.sign( x_speed );
x = Math.min( Math.max( x, -rect_size ), canvas.width );
}
if( y > canvas.height || y < -rect_size ) {
y_speed = Math.random() * max_speed * -Math.sign( y_speed )
y = Math.min( Math.max( y, -rect_size ), canvas.height );
}
// we change the transformation matrix of our context
ctx.setTransform( 1, 0, 0, 1, x, y );
// we thus always draw at coords 0,0
ctx.fillRect( 0, 0, rect_size, rect_size );
ctx.strokeRect( 0, 0, rect_size, rect_size );
requestAnimationFrame( anim );
}
function clear() {
// since we changed the tranform matrix we need to reset it to identity
ctx.setTransform( 1, 0, 0, 1, 0, 0 );
ctx.clearRect( 0, 0, canvas.width, canvas.height );
}
}
<canvas id="canvas" width="300" height="300"></canvas>
We can even detach the path declaration from the filling by changing the transformation matrix after we declared the sub-path and of course by replacing the shorthand fillRect() with beginPath(); rect(); fill()
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = begin; //!\ ALWAYS WAIT FOR YOUR IMAGE TO LOAD BEFORE DOING ANYTHING WITH IT!
img.crossOrigin = "anonymous";
img.src = "https://upload.wikimedia.org/wikipedia/commons/f/f7/Cool_bunny_sprite.png";
function begin() {
const rect_size = 244;
const max_speed = 5;
let x_speed = Math.random() * max_speed;
let y_speed = Math.random() * max_speed;
ctx.fillStyle = ctx.createPattern( img, 'repeat' );
let x = 0;
let y = 0;
requestAnimationFrame( anim );
function anim( now ) {
clear();
x = (x + x_speed);
y = (y + y_speed);
if( x > canvas.width || x < -rect_size ) {
x_speed = Math.random() * max_speed * -Math.sign( x_speed );
x = Math.min( Math.max( x, -rect_size ), canvas.width );
}
if( y > canvas.height || y < -rect_size ) {
y_speed = Math.random() * max_speed * -Math.sign( y_speed )
y = Math.min( Math.max( y, -rect_size ), canvas.height );
}
// we declare the sub-path first, with identity matrix applied
ctx.beginPath();
ctx.rect( 50, 50, rect_size, rect_size );
// we change the transformation matrix of our context
ctx.setTransform( 1, 0, 0, 1, x, y );
// and now we fill
ctx.fill();
ctx.stroke();
requestAnimationFrame( anim );
}
function clear() {
// since we changed the tranform matrix we need to reset it to identity
ctx.setTransform( 1, 0, 0, 1, 0, 0 );
ctx.clearRect( 0, 0, canvas.width, canvas.height );
}
}
<canvas id="canvas" width="300" height="300"></canvas>
But in your case, it sounds that transforming the whole drawing is the easiest and most idiomatic way to follow. Not too sure why you are modifying your x and y coordinates in relation to the camera. Generally, if we use a camera object it's for the objects in the scene don't have to care about it, and stay relative to the world.

Related

HTML5 Canvas: how i can deal with inverted translate() after rotation?

I need to apply several matrix transformations before drawing a shape, however (if on somewhere) I use rotate() the coordinates are inverted and/or reversed and cannot continue without knowing if the matrix was previously rotated.
How can solve this problem?
Example:
<canvas width="300" height="300"></canvas>
<script>
let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");
ctx.fillStyle = "silver";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, canvas.height/2);
ctx.lineTo(canvas.width, canvas.height/2);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(canvas.width/2, 0);
ctx.lineTo(canvas.width/2, canvas.height);
ctx.stroke();
ctx.translate(150, 150);
ctx.rotate(-90 * 0.017453292519943295);
ctx.translate(-150, -150);
// move the red rectangle 100px to the left (top-left)
// but instead is moved on y axis (right-bottom)
ctx.translate(-100, 0);
// more matrix transformations
// ....
// ....
// now finally draw the shape
ctx.fillStyle = "red";
ctx.fillRect(150, 150, 100, 50);
</script>
Can be this Translation after rotation the solution?
It looks like you aren't resetting the canvas matrix each time you make a new transformation.
The Canvas API has the save() and restore() methods. Canvas states are stored on a stack. Every time the save() method is called, the current drawing state is pushed onto the stack. A drawing state consists of transformations that have been applied along with the attributes of things like the fillStyle. When you call restore(), the previous settings are restored.
// ...
ctx.save(); // save the current canvas state
ctx.translate(150, 150);
ctx.rotate(-90 * 0.017453292519943295);
ctx.translate(-150, -150);
ctx.restore(); // restore the last saved state
// now the rectangle should move the correct direction
ctx.translate(-100, 0);
Check out this link for more information on the save and restore methods.
OK finally, i solved the problem by rotating the translation point before applying it. This function does the trick:
function helperRotatePoint(point, angle) {
let s = Math.sin(angle);
let c = Math.cos(angle);
return { x: point.x * c - point.y * s, y: point.x * s + point.y * c};
}
rotating the translation point using the inverted angle I obtain the corrected translation
helperRotatePoint(translation_point, -rotation_angle);
working code:
let canvas = document.querySelector("canvas");
// proper size on HiDPI displays
canvas.style.width = canvas.width;
canvas.style.height = canvas.height;
canvas.width = Math.floor(canvas.width * window.devicePixelRatio);
canvas.height = Math.floor(canvas.height * window.devicePixelRatio);
let ctx = canvas.getContext("2d");
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
ctx.fillStyle = "whitesmoke";
ctx.fillRect(0, 0, canvas.width, canvas.height);
class UIElement {
constructor(x, y, width, height, color) {
// PoC
this.draw_pos = {x, y};
this.draw_size = {width, height};
this.color = color;
this.rotate = 0;
this.scale = {x: 1, y: 1};
this.translate = {x: 0, y: 0};
this.skew = {x: 0, y: 0};
this.childs = [];
}
addChild(uielement) {
this.childs.push(uielement);
}
helperRotatePoint(point, angle) {
let s = Math.sin(angle);
let c = Math.cos(angle);
return {
x: point.x * c - point.y * s,
y: point.x * s + point.y * c
};
}
draw(cnvs_ctx, parent_x, parent_y) {
// backup current state
cnvs_ctx.save();
let elements_drawn = 1;// "this" UIElement
// step 1: calc absolute coordinates
let absolute_x = parent_x + this.draw_pos.x;
let absolute_y = parent_y + this.draw_pos.y;
// step 2: apply all transforms
if (this.rotate != 0) {
cnvs_ctx.translate(absolute_x, absolute_y)
cnvs_ctx.rotate(this.rotate);
cnvs_ctx.translate(-absolute_x, -absolute_y);
// rotate translate point before continue
let tmp = this.helperRotatePoint(this.translate, -this.rotate);
// apply rotated translate
cnvs_ctx.translate(tmp.x, tmp.y);
} else {
cnvs_ctx.translate(this.translate.x, this.translate.y);
}
cnvs_ctx.scale(this.scale.x, this.scale.y);
cnvs_ctx.transform(1, this.skew.y, this.skew.x, 1, 0, 0);
// step 3: self draw (aka parent element)
cnvs_ctx.fillStyle = this.color;
cnvs_ctx.fillRect(absolute_x, absolute_y, this.draw_size.width, this.draw_size.height);
// step 4: draw childs elements
for (let i = 0; i < this.childs.length ; i++) {
elements_drawn += this.childs[i].draw(
cnvs_ctx, absolute_x, absolute_y
);
}
// done, restore state
cnvs_ctx.restore();
return elements_drawn;
}
}
// spawn some ui elements
var ui_panel = new UIElement(120, 50, 240, 140, "#9b9a9e");
var ui_textlabel = new UIElement(10, 10, 130, 18, "#FFF");
var ui_image = new UIElement(165, 25, 90, 60, "#ea9e22");
var ui_textdesc = new UIElement(17, 46, 117, 56, "#ff2100");
var ui_icon = new UIElement(5, 5, 10, 10, "#800000");
ui_panel.addChild(ui_textlabel);
ui_panel.addChild(ui_image);
ui_panel.addChild(ui_textdesc);
ui_textdesc.addChild(ui_icon);
// add some matrix transformations
ui_textdesc.skew.x = -0.13;
ui_textdesc.translate.x = 13;
ui_image.rotate = -90 * 0.017453292519943295;
ui_image.translate.y = ui_image.draw_size.width;
ui_panel.rotate = 15 * 0.017453292519943295;
ui_panel.translate.x = -84;
ui_panel.translate.y = -50;
// all ui element elements
ui_panel.draw(ctx, 0, 0);
<canvas width="480" height="360"></canvas>

How do I draw a shape and then declare it a variable?

<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="350" height="300"
style="border:6px solid black;">
</canvas>
<script>
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.strokeStyle = 'gold';
ctx.strokeRect(20, 10, 160, 100);
</script>
</body>
</html>
Now, I want to go ahead and turn this drawn rectangle:
ctx.strokeStyle = 'gold';
ctx.strokeRect(20, 10, 160, 100);
Into a variable that I can just simply name "Rectangle" I can easily modify and call it out throughout my project. How can I do that? thank you!
You could use Path2D
Using Path2D to create paths and render them as needed is convenient and from a rendering standpoint paths are a little quicker as the sub paths do not need to be created every time you render the path.
It is best to create the sub paths around the origin (0,0) so you can easily move, rotate and scale them as needed.
Example creating some paths with different content
function createRect() {
const path = new Path2D();
path.rect(-70, -45, 140, 90); // add the sub path;
return path;
}
function createCircle() {
const path = new Path2D();
path.arc(0, 0, 50, 0, Math.PI * 2); // add the sub path;
return path;
}
function createRandLines() {
const path = new Path2D();
var i = 10;
while(i--) {
path.moveTo(Math.random() * 20 - 10, Math.random() * 20 - 10);
path.lineTo(Math.random() * 20 - 10, Math.random() * 20 - 10);
}
return path;
}
To create the paths
const myCircle = createCircle();
const myRect = createCircle();
const myLines1 = createRandLines();
const myLines2 = createRandLines();
You can then render any of the paths with a single function.
function strokePath(path, x, y, lineWidth = ctx.lineWidth, color = ctx.strokeStyle) { // defaults to current style
ctx.setTransform(1, 0, 0, 1, x, y); // position the path so its (0,0) origin is at x,y
ctx.lineWidth = lineWidth;
ctx.strokeStyle = color;
ctx.stroke(path);
}
Passing the path position style and line width to draw the path.
const W = ctx.canvas.width;
const H = ctx.canvas.height;
strokePath(myCircle, Math.random() * W, Math.random() * H);
strokePath(myRect, Math.random() * W, Math.random() * H);
strokePath(myLines1, Math.random() * W, Math.random() * H);
strokePath(myLines2, Math.random() * W, Math.random() * H);
Example
A more detailed draw function and some organisation in regards to the create path functions.
The example creates 4 paths once and then draws them many times, randomly positioned, rotated, scaled, alpha faded, colored, and filled.
const W = canvas.width;
const H = canvas.height;
const ctx = canvas.getContext("2d");
ctx.lineCap = ctx.lineJoin = "round";
// Some math utils
Math.TAU = Math.PI * 2;
Math.rand = (m = 0, M = 1) => Math.random() * (M - m) + m;
Math.randItem = array => array[Math.random() * array.length | 0];
const FACE = [[-3,-38,-34,-32,-47,-16,-48,15,-36,34,-5,43,32,38,47,12,47,-21,25,-35],[-31,-19,-42,-6,-32,1,-9,-6,-6,-24],[5,-24,3,-6,29,2,40,-5,33,-19],[-30,15,-14,32,12,31,29,15,15,15,-2,23,-17,16],[-28,-14,-29,-6,-18,-9,-17,-15],[11,-17,12,-8,20,-6,22,-13,18,-16],[2,-39,0,-53,-9,-60],[-14,17,-16,26,-7,28,-5,22],[2,21,1,28,11,27,13,16]];
// Object to hold path types
const paths = {
rect() {
const path = new Path2D();
path.rect(-20, -10, 40, 20); // add the sub path;
return path;
},
ellipse() {
const path = new Path2D();
path.ellipse(0, 0, 20, 10, 0, 0, Math.TAU); // add the sub path;
return path;
},
randLines() {
const path = new Path2D();
var i = 10;
while (i--) {
path.moveTo(Math.rand(-20, 20), Math.rand(-20, 20));
path.lineTo(Math.rand(-20, 20), Math.rand(-20, 20));
}
return path;
},
face() {
const path = new Path2D();
FACE .forEach(sub => { // each sub path
let i = 0;
path.moveTo(sub[i++] / 3, sub[i++] / 3);
while (i < sub.length) { path.lineTo(sub[i++] / 3, sub[i++] / 3) }
path.closePath();
});
return path;
}
};
// Function to draw scaled, rotated, faded, linewidth, colored path
function strokePath(path, x, y, scale, rotate, alpha, lineWidth, color, fillColor) {
ctx.lineWidth = lineWidth * (1 / scale); //Scale line width by inverse scale to ensure the pixel size is constant
ctx.setTransform(scale, 0, 0, scale, x, y); // position the path so its (0,0) origin is at x,y
ctx.rotate(rotate);
if (fillColor) {
ctx.globalAlpha = 1;
ctx.fillStyle = fillColor;
ctx.fill(path, "evenodd");
}
ctx.globalAlpha = alpha;
ctx.strokeStyle = color;
ctx.stroke(path);
}
// create some paths and colors
const pathArray = [paths.ellipse(), paths.rect(), paths.randLines(), paths.face()];
const colors = "#F00,#FA0,#0B0,#0AF,#00F,#F0A,#000,#888".split(",");
drawRandomPath();
function drawRandomPath() {
strokePath(
Math.randItem(pathArray), // random path
Math.rand(0, W), Math.rand(0, H), // random pos
Math.rand(0.25, 1), // random scale
Math.rand(0, Math.TAU), // random rotate
Math.rand(0.5, 1), // random alpha
1, // constant lineWidth
Math.randItem(colors), // random color
Math.rand() < 0.2 ? "#EED" : undefined, // Fill 1 in 5 with white
);
setTimeout(drawRandomPath, 250); // draw another path every quarter second.
}
* {margin:0px}
canvas {border:1px solid}
<canvas id="canvas" width="600" height="190"></canvas>
You can not do that with current standards unfortunately, you will have to redraw the shape, you can do something like:
var shape = x:10,y:20,width:20,height:40
clear the canvas and redraw with created variable:
shape.width = 100;
ctx.rect(shape.x,shape.y,shape.width,shape.height);

Apply grayscale and sepia filters on mousemove - canvas

I am trying to apply grayscale and sepia filters on canvas at the time of mouseMove.
Am using CanvasRenderingContext2D.filter for applying the filters.
Here's the sample code
var radgrad = this.ctx.createRadialGradient(x, y, 50 / 8, x, y, 50 / 2);
radgrad.addColorStop(0, 'rgb(0, 0, 0)');
radgrad.addColorStop(1, 'rgb(0, 0, 0, 1)');
this.ctx.filter = "grayscale(100%) blur(5px) opacity(50%)";
this.ctx.fillStyle = radgrad;
this.ctx.beginPath();
this.ctx.arc(x, y, 50, 0, Math.PI * 2);
this.ctx.fill();
Problem is when I am trying to apply grayscale am not able to achieve it but the blur(5px) is getting applied.
Any solution how to apply grayscale or sepia filter in the above method.
Here's a sample fiddle
Any lead on the solution will be helpful. Thanks
I am not too clear as to what you want, so I'll assume you want something cumulative, as in moving over the same position twice will apply the filter twice.
To do this, the easiest is to create a CanvasPattern from your image. This way you'll be able to fill sub-path using that image as fillStyle, and in the mean time apply your filters on this new drawing:
const img = new Image();
img.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/5/58/Sunset_2007-1.jpg/1024px-Sunset_2007-1.jpg";
img.onload = begin;
const canvas = document.getElementById( 'canvas' );
const ctx = canvas.getContext( '2d' );
const rad = 25;
function begin() {
canvas.width = img.width;
canvas.height = img.height;
// first draw the original image
ctx.drawImage( img, 0, 0 );
// create a CanvasPattern from it
const patt = ctx.createPattern(img, 'no-repeat');
// set the fillStyle to this pattern
ctx.fillStyle = patt;
// and the filter
ctx.filter = "grayscale(100%) blur(5px) opacity(50%)";
// now at each mousemove
document.onmousemove = e => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// we just draw a new arc
ctx.beginPath();
ctx.arc( x, y, rad, 0, Math.PI * 2 );
// this will use the filtered pattern
ctx.fill();
};
}
<canvas id="canvas"></canvas>
In case you didn't want it to be cumulative (like a scratch-card), then you could create a single big sub-path and redraw everything at every frame.
const img = new Image();
img.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/5/58/Sunset_2007-1.jpg/1024px-Sunset_2007-1.jpg";
img.onload = begin;
const canvas = document.getElementById( 'canvas' );
const ctx = canvas.getContext( '2d' );
const rad = 25;
const points = [];
const filter = "grayscale(100%) blur(5px) opacity(50%)";
function begin() {
canvas.width = img.width;
canvas.height = img.height;
const patt = ctx.createPattern(img, 'no-repeat');
ctx.fillStyle = patt;
draw();
// now at each mousemove
document.onmousemove = e => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// store that point
points.push( { x, y } );
// and redraw
draw();
};
}
function draw() {
// remove the filter
ctx.filter = "none";
// so we can draw the background untouched
ctx.drawImage( img, 0, 0 );
// we'll now compose a big sub-path
ctx.beginPath();
points.forEach( ({ x, y }) => {
ctx.moveTo( x, y );
ctx.arc( x, y, rad, 0, Math.PI * 2 )
});
// with the filter
ctx.filter = filter;
ctx.fill();
}
<canvas id="canvas"></canvas>
Note that this code assumes you are on a modern browser which does throttle the mouse events to frame rate. If you are targetting older browsers, you may need to do it yourself.

Rotate individual objects in canvas?

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>

HTML5 Canvas scaling an image animation

I've created a straight forward linear HTML5 animation but I now want the image to continuously scale bigger then smaller until the end of the canvas' height.
What's the best way to do this?
Here's a JSFIDDLE
window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.oRequestAnimationFrame;
var canvas = document.getElementById('monopoly-piece');
var context = canvas.getContext('2d');
var img = document.createElement("img");
img.src = "images/tophat.png";
img.shadowBlur = 15;
img.shadowColor = "rgb(0, 0, 0)";
var xSpeed = 0; //px per ms
var ySpeed = 0.15;
function animate(nowTime, lastTime, xPos, yPos) {
// update
if ((img.style.height + yPos) > canvas.height) {
xPos = 0;
yPos = 0;
}
var timeDelta = nowTime - lastTime;
var xDelta = xSpeed * timeDelta;
var yDelta = ySpeed * timeDelta;
// clear
context.clearRect(0, 0, canvas.width, canvas.height);
// shadow
context.shadowOffsetX = 3;
context.shadowOffsetY = 7;
context.shadowBlur = 4;
context.shadowColor = 'rgba(0, 0, 0, 0.4)';
//draw img
context.drawImage(img, xPos, yPos);
if (yPos > canvas.height - img.height ) {
addMarker();
}
else {
requestAnimationFrame(
function(timestamp){
animate(timestamp, nowTime, xPos + xDelta, yPos + yDelta);
}
);
}
}
animate(0, 0, -10, 0);
Here is my current code which animates an image from the top of the canvas element to the bottom and then stops.
Thanks.
context.DrawImage has additional parameters that let you scale your image to your needs:
// var scale is the scaling factor to apply between 0.00 and 1.00
// scale=0.50 will draw the image half-sized
var scale=0.50;
// var y is the top position on the canvas where you want to drawImage your image
var y=0;
context.drawImage(
// draw img
img,
// clip a rectangular portion from img
// starting at [top,left]=[0,0]
// and with width,height == img.width,img.height
0, 0, img.width, img.height,
// draw that clipped portion of of img on the canvas
// at [top,left]=[0,y]
// with width scaled to img.width*scale
// and height scaled to img.height*scale
0, y, img.width*scale, img.height*scale
)
Here's an example you can start with and fit to your own design needs:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
var scale = 1;
var scaleDirection = 1
var iw, ih;
var y = 1;
var yIncrement = ch / 200;
var img = new Image();
img.onload = start;
img.src = "https://dl.dropboxusercontent.com/u/139992952/multple/sun.png";
function start() {
iw = img.width;
ih = img.height;
requestAnimationFrame(animate);
}
function animate(time) {
if (scale > 0) {
requestAnimationFrame(animate);
}
ctx.clearRect(0, 0, cw, ch);
ctx.drawImage(img, 0, 0, iw, ih, 0, y, iw * scale / 100, ih * scale / 100);
scale += scaleDirection;
if (scale > 100) {
scale = 100;
scaleDirection *= -1;
}
y += yIncrement;
}
$("#test").click(function() {
scale = y = scaleDirection = 1;
requestAnimationFrame(animate);
});
body{ background-color: ivory; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id="test">Animate Again</button>
<br>
<canvas id="canvas" width=80 height=250></canvas>

Categories