Animated image transition with HTML5 canvas - javascript

Trying to wrap my head around the HTML5 canvas, I thought I'd create an image carousel, where images would be changed by an opacity gradient sweep, i.e. the same thing as in my fiddle here, only with canvas. I managed to come up with this fiddle, but I can't understand at all what's happening, or rather, why nothing is.
Here's the code:
var outputCanvas = document.getElementById('output'),
ctx = outputCanvas.getContext('2d'),
eWidth = 50,
speed = 5,
cWidth = 480,
img = document.getElementById('newimg'),
x = 0, y = 0,
reqAnimFrame = window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame;
ctx.drawImage(img, 0, 0, img.width, img.height);
ctx.globalCompositeOperation = "destination-out";
function draw() {
console.log(x);
gradient = ctx.createLinearGradient(x, 0, x+eWidth, 0);
gradient.addColorStop(0, "rgba(255, 255, 255, 0)");
gradient.addColorStop(1, "rgba(255, 255, 255, 1)");
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, img.width, img.height);
}
function animate() {
if (x < 480) {
x += Math.floor((cWidth / 1000) * speed);
console.log(x);
draw();
reqAnimFrame(animate);
}
}
reqAnimFrame(animate);
Calling the draw function by itself it seems to work, but once I start firing it with RequestAnimationFrame it just stops working. The gradient gets drawn once, but even though x is updated in the animation loop, the gradient stays put.
I guess there's something I'm just not getting about how canvas and RequestAnimationFrame work.
Note that I'm not looking for a script or library that does the same thing, but rather I'm hoping to actually understand how canvas works, and in particular why my script doesn't.

Here's one way to do a wipe transition between 2 images using Canvas Compositing:
Original Images (before & after):
Canvas during gradient wipe-transition between the images:
create a transparent-to-opaque gradient that is eWidth pixels wide.
clear the canvas
draw the gradient
fill all pixels to the right of the gradient with opaque
draw the first image with source-in compositing. This will display the first image only where the gradient has non-transparent pixels.
draw the second image with 'destination-over' compositing. This will display the second image "under" the existing first image.
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw,ch;
var x=0;
var eWidth=100;
var img1=new Image();
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/sailboat.png";
function start(){
cw=canvas.width=img.width;
ch=canvas.height=img.height;
img1.onload=function(){
requestAnimationFrame(animate);
};
img1.src="https://dl.dropboxusercontent.com/u/139992952/multple/sailboat1.png";
}
function draw() {
// create gradient
gradient = ctx.createLinearGradient(x-eWidth,0, x,0);
gradient.addColorStop(0, "rgba(0,0,0, 0)");
gradient.addColorStop(1, "rgba(0,0,0, 1)");
// save the unaltered canvas context
ctx.save();
// clear the canvas
ctx.clearRect(0,0,cw,ch);
// gradient zone
ctx.fillStyle = gradient;
ctx.fillRect(x-eWidth,0,eWidth,ch);
// fully original right of x
ctx.fillStyle='black';
ctx.fillRect(x,0,cw,ch);
// original image with gradient "dissolve" on left
// set compositing to source-in
ctx.globalCompositeOperation='source-in';
ctx.drawImage(img,0,0);
// revealed image
ctx.globalCompositeOperation='destination-over';
ctx.drawImage(img1,0,0);
// restore the context to its unaltered state
ctx.restore();
}
function animate() {
if (x<cw+eWidth){ requestAnimationFrame(animate); }
x+=5;
draw();
}
$('#again').click(function(){
x=0;
requestAnimationFrame(animate);
});
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Wipe transition between images using canvas</h4>
<button id=again>Again</button><br><br>
<canvas id="canvas" width=300 height=300></canvas>

Related

Fastest way of fading out entire contents of a canvas to transparency, not other color

I want to create visualization, where whatever is drawn fades away slowly. This should be continuous, so that content added later will be less faded than old content, like in this picture:
I don't want to clear entire canvas, I would like to just make everything on it more transparent - then draw any new objects.
My instinct would be to do this in each frame:
ctx.fillStyle = "rgba(0,0,0,0)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
However the problem is that canvas doesn't blend alpha - it uses it for blending the other colors.
How can I get canvas to continuously decrease opacity of everything drawn in it?
You're almost definitely looking for canvas compositing operations! It looks like "destination-in" should achieve what you're looking for.
let canvas = document.getElementsByTagName('canvas')[0];
let ctx = canvas.getContext('2d');
let rand = n => Math.floor(Math.random() * n);
setInterval(() => {
ctx.beginPath();
ctx.arc(rand(300), rand(120), rand(60), Math.PI * 2, 0);
ctx.fillStyle = `rgba(${rand(256)}, ${rand(256)}, ${rand(256)}, 1)`;
ctx.globalCompositeOperation = 'source-over';
ctx.fill();
}, 150);
let fadeOut = () => {
let fadeAmount = 0.002;
// Note that the colour here doesn't matter! Only the alpha matters.
// The colour here is red, but you'll see no red appear
ctx.fillStyle = `rgba(255, 0, 0, ${1 - fadeAmount})`;
ctx.globalCompositeOperation = 'destination-in';
ctx.fillRect(0, 0, 300, 120);
requestAnimationFrame(fadeOut);
};
requestAnimationFrame(fadeOut);
canvas { border: 3px solid #808080; background-color: #000000; }
<canvas width="300" height="120"></canvas>
Note: When you set ctx.globalCompositeOperation, it will apply until you either do ctx.restore(), or set ctx.globalCompositeOperation to a new value! This is a bit of a gotcha.
why you don't decrease the opacity of the canvas and when it reach 0 you erase everything from the canvas then reset the opacity value to 1

Canvas - clip image and use composite operation "multiply" in Firefox doesn't work as in Chrome

I am working on an app which uses canvas. I draw some shapes, one over another which can be filled with colours or images and overlap each other. I use clip() to clip images to fit shape, but when I change globalCompositeOperation to multiply it makes clip() stop working. I've created a simple example to present what my problem is.
Please try to open it in Google Chrome and in Mozilla Firefox. While in Chrome image can be clipped then set as a multiply of lower layer, in Mozilla after applying multiply, clipping stops working. Any ideas to solve this issue?
// Grab the Canvas and Drawing Context
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
// Create an image element
var img = document.createElement('IMG');
// When the image is loaded, draw it
img.onload = function () {
ctx.fillStyle="red"
ctx.fillRect(0,0,100,100)
// 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.globalCompositeOperation="multiply";
ctx.drawImage(img, 0, 0);
// Undo the clipping
ctx.restore();
}
// Specify the src to load the image
img.src = "http://i.imgur.com/gwlPu.jpg";
body {
background: #CEF;
}
<canvas id="c" width="200" height="158"></canvas>
This sounds like a known issue on Windows version of FireFox...
Only FF devs could have provided a real fix, but since the bug has been reported at least two years ago, I wouldn't expect it to be fixed anytime soon.
Now, to workaround the issue, you could draw in two steps:
on a first offscreen canvas do the clipping,
then do the blending on the main canvas using the now clipped one.
This may incur a little memory overhead, but having a second canvas ready might also come handy if you do a lot of compositing/blending.
// Grab the Canvas and Drawing Context
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
// create an off-screen copy of the context
var ctx2 = canvas.cloneNode().getContext('2d');
// Create an image element
var img = document.createElement('IMG');
// When the image is loaded, draw it
img.onload = function () {
// Do the clipping on the offscreen canvas
ctx2.save();
// Create a shape, of some sort
ctx2.beginPath();
ctx2.moveTo(10, 10);
ctx2.lineTo(100, 30);
ctx2.lineTo(180, 10);
ctx2.lineTo(200, 60);
ctx2.arcTo(180, 70, 120, 0, 10);
ctx2.lineTo(200, 180);
ctx2.lineTo(100, 150);
ctx2.lineTo(70, 180);
ctx2.lineTo(20, 130);
ctx2.lineTo(50, 70);
ctx2.closePath();
ctx2.clip();
// draw the image
ctx2.drawImage(img, 0, 0);
ctx2.restore();
// Now go back on the main canvas
ctx.fillStyle = "red";
ctx.fillRect(0,0,100,100);
// do the blending with our now clipped image
ctx.globalCompositeOperation="multiply";
ctx.drawImage(ctx2.canvas,0,0);
}
// Specify the src to load the image
img.src = "http://i.imgur.com/gwlPu.jpg";
body {
background: #CEF;
}
<canvas id="c" width="200" height="158"></canvas>
But for this exact case, i.e multiply blending, you can actually simply get rid of the clipping altogether on a single canvas.
Indeed, if I'm not mistaken, multiply doesn't really cares of which layer is top or bottom, the result will just be the same.
So you could simply do your compositing first, and as a final step do the blending over the composited image.
// Grab the Canvas and Drawing Context
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
// Create an image element
var img = document.createElement('IMG');
// When the image is loaded, draw it
img.onload = function () {
ctx.fillStyle="red";
// 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();
// fill the current path
ctx.fill();
// draw only where our previous shape was drawn
ctx.globalCompositeOperation="source-atop";
ctx.drawImage(img, 0, 0);
// multiply blending should work the same in two directions
ctx.globalCompositeOperation="multiply";
ctx.fillRect(0,0,100,100);
}
// Specify the src to load the image
img.src = "http://i.imgur.com/gwlPu.jpg";
body {
background: #CEF;
}
<canvas id="c" width="200" height="158"></canvas>
And of course, you could very well get rid of clipping thanks to compositing and use a second offscreen canvas. (Probably my personal favorite).

Change color Image in Canvas

I want to change color a Image in canvas
this is the Image
You can see there is a Image transparent I was try using PutImgData but my transparent is changing color
Is there anyway to change color the car and money only ?
I was using this code :
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
image = document.getElementById("testImage");
canvas.height = canvas.width = 100;
ctx.fillStyle = 'red';
ctx.fillRect(10,10,20,10);
ctx.drawImage(image,0,0);
var imgd = ctx.getImageData(0, 0, 45, 45),
pix = imgd.data;
for (var i = 0, n = pix.length; i <n; i += 4) {
if(pix[i+3]==0)
{continue;}
pix.length[i]=r|pix.length[i];
pix.length[i+1]=g|pix.length[i+1];
pix.length[i+2]=b|pix.length[i+2];
pix[i + 3] = 255;
}
ctx.putImageData(imgd, 0, 0);
To mix manually you would have to apply a different formula to mix foreground (new color) and background (image) to preserve anti-aliased pixels (and just in case: the image included in the question is not actually transparent, but I guess you just tried to illustrate transparency using the solid checkerboard background?).
I would suggest a different approach which is CORS safe and much faster (and simpler) -
There are a couple of ways to do this: one is to draw in the color you want, then set composite mode to destination-in and then draw the image, or draw the image, set composite mode to source-in and then draw the color.
Example using the first approach coloring the following image blue:
var img = new Image; img.onload = draw; img.src = "//i.stack.imgur.com/cZ0gC.png";
var ctx = c.getContext("2d");
function draw() {
// draw color
ctx.fillStyle = "#09f";
ctx.fillRect(0, 0, c.width, c.height);
// set composite mode
ctx.globalCompositeOperation = "destination-in";
// draw image
ctx.drawImage(this, 0, 0);
}
<canvas id=c></canvas>
Example using second approach:
var img = new Image; img.onload = draw; img.src = "//i.stack.imgur.com/cZ0gC.png";
var ctx = c.getContext("2d");
function draw() {
// draw image
ctx.drawImage(this, 0, 0);
// set composite mode
ctx.globalCompositeOperation = "source-in";
// draw color
ctx.fillStyle = "#09f";
ctx.fillRect(0, 0, c.width, c.height);
}
<canvas id=c></canvas>
To reset comp. mode back to normal use:
// reset comp. mode
ctx.globalCompositeOperation = "source-over";
As with getImageData(), the drawback with this technique is that your canvas must only hold the content of this image while doing this process. A workaround if the image needs to be colored and mixed with other content is to use an off-screen canvas to do the processing, then draw that canvas back onto the main canvas.

How to avoid permanent particle trails on html5 canvas?

There are thousands of moving particles on an HTML5 canvas, and my goal is to draw a short fading trail behind each one. A nice and fast way to do this is to not completely clear the canvas each frame, but overlay it with semi-transparent color. Here is an example with just one particle:
var canvas = document.getElementById('display');
var ctx = canvas.getContext('2d');
var displayHeight = canvas.height;
var backgroundColor = '#000000';
var overlayOpacity = 0.05;
var testParticle = {
pos: 0,
size: 3
};
function render(ctx, particle) {
ctx.globalAlpha = overlayOpacity;
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalAlpha = 1.0;
ctx.fillStyle = '#FFF';
ctx.fillRect(particle.pos, displayHeight / 2, particle.size, particle.size);
}
function update(particle) {
particle.pos += 1;
}
// Fill with initial color
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
function mainLoop() {
update(testParticle);
render(ctx, testParticle);
requestAnimationFrame(mainLoop);
}
mainLoop();
<canvas id="display" width="320" height="240"></canvas>
There is an apparent problem: with low opacity values, the trail never fades away completely. You can see the horizontal line that (almost) does not fade in my single-particle example. I understand why this happens. ColorA overlayed by semi-transparent ColorB is basically a linear interpolation, and ColorA never fully converges to ColorB if we repeatedly do the following:
ColorA = lerp(ColorA, ColorB, opacityOfB)
My question is, what can I do to make it converge to the background color, so that trails don't remain there forever? Using WebGL or drawing trails manually are not valid options (because of compatibility and performance reasons respectively). One possibility is to loop over all canvas pixels and manually set pixels with low brightness to background color, although it may get expensive for large canvases. I wonder if there are better solutions.
As a workaround which could work in some cases is to set the overlayOpacity up to 0.1 (this value converges) but draw it only every x times and not in every render call.
So when drawn only every other time it keeps more or less the same trail length.
var renderCount = 0;
var overlayOpacity = 0.1;
function render(ctx, particle) {
if((renderCount++)%2 == 0) {
ctx.globalAlpha = overlayOpacity;
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
ctx.globalAlpha = 1.0;
ctx.fillStyle = '#FFF';
ctx.fillRect(particle.pos, displayHeight / 2, particle.size, particle.size);
}
Obviously the disadvantage is that it looks more jerked and perhaps this may not be acceptable in your case.
Best solution is to use the composite operation "destination-out" and fade to a transparent background. Works well for fade rates down to globalAlpha = 0.01 and event a little lower 0.006 but it can be troublesome below that. Then if you need even slower fade just doe the fade every 2nd or 3rd frame.
ctx.globalAlpha = 0.01; // fade rate
ctx.globalCompositeOperation = "destination-out" // fade out destination pixels
ctx.fillRect(0,0,w,h)
ctx.globalCompositeOperation = "source-over"
ctx.globalAlpha = 1; // reset alpha
If you want a coloured background you will need to render the animation on an offscreen canvas and render it over the onscreen canvas each frame. Or make the canvas background the colour you want.
If someone struggles with this, here is a workaround that worked for me:
// Do this instead of ctx.fillStyle some alpha value and ctx.fillRect
if(Math.random() > 0.8){
ctx.fillStyle = 'rgba(255, 255, 255, '+getRandomNumber(0.1,0.001)+')';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// Define this helper function somewhere in your code
function getRandomNumber(minValue, maxValue) {
return Math.random() * (maxValue - minValue) + minValue;
}
It also works for different colored backgrounds. Adjust trail length by playing around with Math.random() > 0.8 and getRandomNumber(0.1,0.001).

Gradient map as opacity in a HTML5 canvas element?

Is there a way to create an opacity map on a canvas element
I am trying to fade a generated image as shown below.
EXAMPLE:
I don't believe there is any way to directly draw an image with a gradiant mask, but you could pre-draw the image to a separate canvas, and use globalCompositeOperation to draw a masking linear gradient, then draw that canvas using drawImage to the main canvas.
Working Example:
var cvs = document.getElementById('cvs');
var ctx = cvs.getContext('2d');
// Draw some background colors.
ctx.fillStyle = "#FF6666";
ctx.fillRect(0, 0, 150, 200);
ctx.fillStyle = "#6666FF";
ctx.fillRect(150, 0, 150, 200);
// Load the image.
img = new Image();
img.onload = function() {
// Create a canvas in memory and draw the image to it.
var icvs = document.createElement('canvas');
icvs.width = img.width;
icvs.height = img.height;
var ictx = icvs.getContext('2d');
ictx.drawImage(img, 0, 0);
// For masking.
ictx.globalCompositeOperation = 'destination-out';
// Draw the masking gradient.
var gradient = ictx.createLinearGradient(0, 0, 0, icvs.height);
gradient.addColorStop(0, "transparent");
gradient.addColorStop(1, "white");
ictx.fillStyle = gradient;
ictx.fillRect(0, 0, icvs.width, icvs.height);
// Draw the separate canvas to the main canvas.
ctx.drawImage(icvs, 25, 25, 250, 150);
};
img.src = '//i.stack.imgur.com/dR8i9.jpg';
<canvas id="cvs" width="300" height="200"></canvas>

Categories