Chroma keying with javascript & jQuery - javascript

Okay, we need your help! We (with our informatics class) are building a digital scratchmap! Like this:
(source: megagadgets.nl)
With your mouse you should be able to scratch out the places you've been to. Now we're stuck. We have a canvas and we draw the image of a world map. Then when the user clicks and drags a stroke gets add on top of the world map.
Now we want to convert the (green drawn) strokes to transparency so we can reveal the image behind it. (Just like scratching out the places you've been to and revealing the map behind it (in colour)).
This is our html:
<body>
<h1>Scratchmap</h1>
<hr>
<canvas id="ball" width="600px" height ="600px">
</canvas>
<canvas id="ball2" width="600px" height ="600px">
</canvas>
</body>
And this is our javascript:
// Set variables
var a_canvas = document.getElementById("ball");
var context = a_canvas.getContext("2d");
var a_canvas2 = document.getElementById("ball2");
var context2 = a_canvas2.getContext("2d");
var img = new Image();
img.onload = function () {
context.drawImage(img, img_x, img_y);
}
img.src = "worldmap.png"
var mouse_pos_x = [];
var mouse_pos_y = [];
var thickness = 0;
var arraycount = 0;
var mouse_down = false;
var mouse_skip = [];
function update() {}
document.body.onmousedown = function () {
mouse_down = true;
var mouseX, mouseY;
if (event.offsetX) {
mouseX = event.offsetX;
mouseY = event.offsetY;
} else if (event.layerX) {
mouseX = event.layerX;
mouseY = event.layerY;
}
mouse_pos_x.push(mouseX);
mouse_pos_y.push(mouseY);
arraycount += 1;
}
document.body.onmouseup = function () {
if (mouse_down) {
mouse_down = false;
mouse_skip.push(arraycount);
}
}
document.body.onmousemove = function () {
if (mouse_down) {
var mouseX, mouseY;
if (event.offsetX) {
mouseX = event.offsetX;
mouseY = event.offsetY;
} else if (event.layerX) {
mouseX = event.layerX;
mouseY = event.layerY;
}
context.drawImage(img, 0, 0);
mouse_pos_x.push(mouseX);
mouse_pos_y.push(mouseY);
context.lineWidth = 2.5;
context.strokeStyle = "#00FF00";
context.moveTo(mouse_pos_x[arraycount - 1], mouse_pos_y[arraycount - 1]);
context.lineTo(mouse_pos_x[arraycount], mouse_pos_y[arraycount]);
context.stroke();
arraycount += 1;
var imgdata = context.getImageData(0, 0, a_canvas.width, a_canvas.height);
var l = imgdata.data.length / 4;
for (var i = 0; i < l; i++) {
var r = imgdata.data[i * 4 + 0];
var g = imgdata.data[i * 4 + 1];
var b = imgdata.data[i * 4 + 2];
if (g < 255) {
imgdata.data[i * 4 + 3] = 0;
}
}
context2.putImageData(imgdata, 0, 0);
}
}
setInterval(update, 10);
Now when we remove the draw_image() the green color becomes yellow on the other canvas. But with the draw_image() nothing gets drawn on the second canvas.
What's going wrong? Or do you have a way to do this with other Javascript or not in javascript at all?
Any help would be appreciated!
Luud Janssen & Friends

You can do this with a slightly different approach:
Set the hidden image as CSS background
Draw the cover image on top using context
Change composite mode to destination-out
Anything now drawn will erase instead of draw revealing the (CSS set) image behind
Live demo
The key code (see demo linked above for details):
function start() {
/// draw top image - background image is already set with CSS
ctx.drawImage(this, 0, 0, canvas.width, canvas.height);
/// KEY: this will earse where next drawing is drawn
ctx.globalCompositeOperation = 'destination-out';
canvas.onmousedown = handleMouseDown;
canvas.onmousemove = handleMouseMove;
window.onmouseup = handleMouseUp;
}
Then it's just a matter of tracking the mouse position and draw any shape to erase that area, for example a circle:
function erase(x, y) {
ctx.beginPath();
ctx.arc(x, y, radius, 0, pi2);
ctx.fill();
}
Random images for illustrative purposes

Related

Canvas HTML fillText letters to not animate only shadows

Only want shadows to animate and keep the fillText from animating due to letters pixelating from getting ran over and over.
var canvas = document.getElementById('canvas')
var ctx = this.canvas.getContext('2d')
var width = canvas.width = canvas.scrollWidth
var height = canvas.height = canvas.scrollHeight
var start;
var j=0;
var makeText = function(){
j+=1
ctx.shadowColor= 'red';
ctx.shadowOffsetX = j; //animate
ctx.shadowOffsetY = j; //animate
ctx.globalAlpha=0.5;
ctx.font = "48px serif";
ctx.fillStyle = "black";
ctx.fillText('hey you', width/2, height / 2); //Only ran once so letters
//don't pixelate!
}
function animateText(timestamp){
var runtime = timestamp - start;
var progress = Math.min(runtime / 1400, 1);
makeText(progress)
if(progress < 1){
requestAnimationFrame(animateText)
}else {
return;
}
}
requestAnimationFrame(function(timestamp){
start = timestamp;
animateText(timestamp)
})
<canvas id="canvas" width=500px height=500px></canvas>
My outcome of the process would only have shadows animate and keeping letters where they are
Just draw your own shadows, here is an example:
var canvas = document.getElementById('canvas')
var ctx = this.canvas.getContext('2d')
ctx.font = "68px serif";
var base = {text: 'hey you', x: 10, y: 60 }
var inc = 2;
var j = 30;
var makeText = function() {
ctx.globalAlpha = 1;
ctx.fillStyle = "black";
ctx.fillText(base.text, base.x, base.y);
}
var makeshadow = function(offset) {
ctx.fillStyle = "red";
for (var i = 0; i < offset; i++) {
ctx.globalAlpha = 1/i;
ctx.fillText(base.text, base.x + i, base.y + i);
}
}
function animateText() {
ctx.clearRect(0, 0, 999, 999)
makeshadow(j);
makeText();
j += inc;
if (j > 35 || j < 3) inc *= -1
}
setInterval(animateText, 50)
<canvas id="canvas" width=300px height=170px></canvas>
And if you add some math in the mix you can get some cool effects:
var canvas = document.getElementById('canvas')
var ctx = this.canvas.getContext('2d')
ctx.font = "68px serif";
var base = {text: '123456', x: 30, y: 80 }
var inc = 5;
var j = 0;
var makeText = function() {
ctx.globalAlpha = 1;
ctx.fillStyle = "black";
ctx.fillText(base.text, base.x, base.y);
}
var makeshadow = function(offset) {
ctx.globalAlpha = 0.05;
ctx.fillStyle = "red";
for (var i = 0; i < offset; i++)
ctx.fillText(base.text, base.x + Math.sin(i/5)*10, base.y + Math.cos(i/5)*15);
}
function animateText() {
ctx.clearRect(0, 0, 999, 999)
makeshadow(j);
makeText();
j += inc;
if (j > 120 || j < 0) inc *= -1
}
setInterval(animateText, 50)
<canvas id="canvas" width=300px height=170px></canvas>
Your main issue (the text pixelisation) is due to you not clearing the canvas between every frames, and drawing again and again over the same position. semi-transparent pixels created by antialiasing mix up to more and more opaque pixels.
But in your situation, it seems that you actually want at-least the shadow to mix up like this.
To do it, one way would be to draw only once your normal text, and to be able to draw only the shadow, behind the current drawing.
Drawing only the shadow of a shape.
One trick to draw only the shadows of your shape is to draw your shape out of the visible viewPort, with shadowOffsets set to the inverse of this position.
var text = 'foo bar';
var ctx = canvas.getContext('2d');
var original_x = 20; // the position it would have been
ctx.font = '30px sans-serif';
var targetPosition = ctx.measureText(text).width + original_x + 2;
// default shadow settings
ctx.shadowColor = 'red';
ctx.shadowBlur = 3;
// just to show what happens
var x = 0;
anim();
function anim() {
if(++x >= targetPosition) {
x=0;
return;
}
// if we weren't to show the anim, we would use 'targetPosition'
// instead of 'x'
ctx.shadowOffsetX = x;
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fillText(text, -x + original_x, 30);
requestAnimationFrame(anim);
}
// restart the anim on click
onclick = function() {
if(x===0)anim();
};
<canvas id="canvas"></canvas>
Once we have this clear shadow, without our shape drawn on it, we can redraw it as we wish.
Drawing behind the current pixels
The "destination-over" compositing option does just that.
So if we put these together, we can draw behind the normal text, and only draw our shadow behind it at each frame, avoiding antialiasing mix-up.
(Note that we can also keep the clean shadow on an offscreen canvas for performances, since shadow is a really slow operation.)
var text = 'foo bar';
var ctx = canvas.getContext('2d');
ctx.font = '48px sans-serif';
var x = 20;
var y = 40;
var shadow = generateTextShadow(ctx, text, x, y, 'red', 5);
ctx.globalAlpha = 0.5;
ctx.fillText(text, x, y);
// from now on we'll draw behind current content
ctx.globalCompositeOperation = 'destination-over';
var shadow_pos = 0;
anim();
// in the anim, we just draw the shadow at a different offset every frame
function anim() {
if(shadow_pos++ > 65) return;
ctx.drawImage(shadow, shadow_pos, shadow_pos);
requestAnimationFrame(anim);
}
// returns a canvas where only the shadow of the text provided is drawn
function generateTextShadow(original_ctx, text, x, y, color, blur, offsetX, offsetY) {
var canvas = original_ctx.canvas.cloneNode();
var ctx = canvas.getContext('2d');
ctx.font = original_ctx.font;
var targetPosition = ctx.measureText(text).width + 2;
// default shadow settings
ctx.shadowColor = color || 'black';
ctx.shadowBlur = blur || 0;
ctx.shadowOffsetX = targetPosition + x +(offsetX ||0);
ctx.shadowOffsetY = (offsetY || 0);
ctx.fillText(text, -targetPosition, y);
return canvas;
}
<canvas id="canvas"></canvas>

Make a shape move up on a canvas

Currently, I have a canvas which is the width and height of your browser. Using this code:
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
var canvas = document.getElementById("canvas");
var width = window.innerWidth;
var height = window.innerHeight;
var circle = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
for(var i = 0; i < numofcirc; i++)
{
name = "circleno" + i;
var name = new Array(3);
name = [height, rndwidth, rndradius, vel]
circles[i] = name;
}
var vel = 2;
var circles = [];
var numofcirc = 1;
var name;
function DrawCircle()
{
rndwidth = Math.floor((Math.random() * width) + 1);
height = height - 13;
rndradius = Math.floor((Math.random() * 15) + 5);
circle.beginPath();
circle.arc(rndwidth, height, rndradius, 0, 2*Math.PI);
circle.fillStyle = "white";
circle.fill();
circle.translate(0,6);
}
function Move()
{
circle.translate(0,6);
requestAnimationFrame(Move);
}
Move();
DrawCircle();
I am able to create a circle placed randomly at the bottom of your screen. The bit of the code that isn't working is this:
function Move()
{
circle.translate(0,6);
requestAnimationFrame(Move);
}
Fireworks();
When DrawCircle(); is called, the circle is drawn on the canvas. Then Move(); is called. Becuase it uses requestAnimationFrame the function Move(); repeats over and over again. I want this code to move that circle drawn ealier up by 6, so it looks like the circle moving up.
If I add the circle.translate(0,6); to the DrawCircle(); function and change the DrawCircle(); function to this:
function DrawCircle()
{
rndwidth = Math.floor((Math.random() * width) + 1);
height = height - 13;
rndradius = Math.floor((Math.random() * 15) + 5);
circle.beginPath();
circle.arc(rndwidth, height, rndradius, 0, 2*Math.PI);
circle.fillStyle = "white";
circle.fill();
circle.translate(0,6);
requestAnimationFrame(Move);
}
DrawCircle();
then it keeps on drawing rows of circles across the screen which are all separated by 6.
Is there any way I can just make one single circle move up on your screen when it is drawn?
Thank you for you help #HelderSepu !
You should look at examples and build from that...
Here is one simple case:
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
canvas.width = canvas.height = 170;
var circles = []
circles.push({color:"red", x:120, y:120, r:15, speed:{x: 0, y: -0.5}})
circles.push({color:"blue", x:80, y:120, r:20, speed:{x: -0.5, y: -2.5}})
circles.push({color:"green", x:40, y:120, r:5, speed:{x: -1.5, y: -1.0}})
function DrawCircle() {
context.clearRect(0, 0, canvas.width, canvas.height);
circles.forEach(function(c) {
c.x += c.speed.x;
c.y += c.speed.y;
context.beginPath();
context.arc(c.x, c.y, c.r, 0, 2 * Math.PI);
context.fillStyle = c.color;
context.fill();
if (c.x + c.r < 0) c.x = canvas.width + c.r
if (c.y + c.r < 0) c.y = canvas.height + c.r
});
window.requestAnimationFrame(DrawCircle);
}
DrawCircle();
<canvas id="canvas"></canvas>
But if you are going to do a lot more animations you should consider using a game engine, there are a lot of great open source ones:
https://github.com/collections/javascript-game-engines
Since you're getting a sequence of circles, it looks like you're not clearing the canvas when a frame is drawn. Simply draw a white rectangle that fills the canvas whenever a new frame is requested, then draw your circle.
The method you provide as an argument to requestAnimationFrame is responsible for drawing a complete image on the canvas which replaces whatever was there during the previous frame.

Is there any way to draw a "streak" (fading "digital phosphor" effect) on HTML5 canvas?

I want to draw a moving dot on an HTML5 canvas that follows a complicated trajectory. This I know how to do; see, for example, the Lorenz attractor as implemented below. But with small dots it is hard to follow. Is there a way to add a blurry trail behind a dot? I can keep past history of drawn points, I just don't know how to make them fade.
In technical terms, I suppose this would be a polyline/curve where the opacity/width/color changes smoothly along the curve. I know how to draw polylines (and can figure out the Bezier curve stuff if I need to), but I don't know how to apply a smooth gradient along a path.
(Digital oscilloscopes solved this "problem" by having a Digital Phosphor Oscilloscope effect where the scope emulated the old analog "phosphor" effect: areas hit by the scope's "beam" would take a while to fade.)
<!DOCTYPE html>
<html>
<head><script type="text/javascript">
document.addEventListener("DOMContentLoaded", function(event) {
var x = 1, y = 0, z = 0, t=0;
function onTick(timestamp)
{
var ctx = document.getElementById('canvas').getContext('2d');
ctx.clearRect(0, 0, 300, 300);
ctx.fillStyle = 'black';
var cx = 150;
var cy = 150;
var r = 5;
var now = timestamp * 0.001;
var dt = now - t;
t = now;
if (dt > 0.1)
dt = 0.1;
// Lorenz attractor
var sigma = 10, rho=28, beta=8/3;
var dxdt = sigma*(y-x);
var dydt = x*(rho-z)-y;
var dzdt = x*y-beta*z;
x += dt*dxdt;
y += dt*dydt;
z += dt*dzdt;
var drawx = cx + r*x;
var drawy = cy + r*y;
var rdot = 2;
ctx.beginPath();
ctx.arc(drawx, drawy, rdot, 0, 2 * Math.PI, true);
ctx.fill();
requestAnimationFrame(onTick);
}
requestAnimationFrame(onTick);
});
</script></head>
<body>
<canvas id="canvas" width="300" height="300"/>
</body>
</html>
Instead of clearing the rectangle each frame, just paint it in an alpha channel to save those previous dots momentarily. I replaced your clearRect with fillRect the fillStyle is white see-through.
Keep in ming you can adjust the alpha channel, this will make the dot stay for longer/short duration. In my code this is the 0.04 in the ctx.fillStyle = "rgba(255, 255, 255, 0.04)";. I just adjusted it lower to make those traces stay for a longer time.
document.addEventListener("DOMContentLoaded", function(event) {
var x = 1, y = 0, z = 0, t=0;
function onTick(timestamp)
{
var ctx = document.getElementById('canvas').getContext('2d');
//ctx.clearRect(0, 0, 300, 300);
ctx.fillStyle = "rgba(255, 255, 255, 0.04)";
ctx.fillRect(0, 0, 300, 300);
ctx.fillStyle = 'black';
var cx = 150;
var cy = 150;
var r = 5;
var now = timestamp * 0.001;
var dt = now - t;
t = now;
if (dt > 0.1)
dt = 0.1;
// Lorenz attractor
var sigma = 10, rho=28, beta=8/3;
var dxdt = sigma*(y-x);
var dydt = x*(rho-z)-y;
var dzdt = x*y-beta*z;
x += dt*dxdt;
y += dt*dydt;
z += dt*dzdt;
var drawx = cx + r*x;
var drawy = cy + r*y;
var rdot = 2;
ctx.beginPath();
ctx.arc(drawx, drawy, rdot, 0, 2 * Math.PI, true);
ctx.fill();
requestAnimationFrame(onTick);
}
requestAnimationFrame(onTick);
});
canvas {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: -1;
}
<canvas id="canvas"></canvas>
Classic trick was to draw a polyline instead single dot, altering color and/or opacity per each vertice of polyline, the brightest would be the furthest along traectory

Make canvas transparent

This is what my body looks like:
body
{
background-image:url('../images/bg.png');
background-repeat:no-repeat;
background-size:fixed 100vw;
background-position:center;
}
The issue is, the canvas is white instead of being transparent. Is there a way to make it transparent so I can place the dna wave on top of a background?
Codepen example
One easy way, is using an offscreen canvas.
First set its context's globalAlpha value to something between 0 and 1, this will determine how fast your previous drawings will disappear.
Then, in the animation loop, before doing the new drawings,
clear the offscreen context,
draw the visible canvas on the offscreen one,
clear the visible canvas
draw back the offscreen one on the visible one
In the process, your image will have lost opacity.
var clear = function(){
// clear the clone canvas
cloneCtx.clearRect(0,0,canvasWidth, canvasHeight)
// this should be needed at init and when canvas is resized but for demo I leave it here
cloneCtx.globalAlpha = '.8';
// draw ou visible canvas, a bit less opaque
cloneCtx.drawImage(context.canvas, 0,0)
// clear the visible canvas
context.clearRect(0,0,canvasWidth, canvasHeight)
// draw back our saved less-opaque image
context.drawImage(clone, 0,0)
}
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
// create an offscreen clone
clone = canvas.cloneNode(),
cloneCtx = clone.getContext('2d'),
canvasWidth = canvas.width =
clone.width =window.innerWidth,
canvasHeight = canvas.height = clone.height = window.innerHeight,
globalTick = 0,
points = [],
pointCount = 12,
pointSpeed = 6,
spacing = canvasWidth / pointCount,
pointCount = pointCount + 2,
verticalPointRange = 60,
randomRange = function(min, max){
return Math.floor( (Math.random() * (max - min + 1) ) + min);
},
iPath,
iPoints;
var Point = function(x, y, alt){
this.x = x;
this.y = y;
this.yStart = y;
this.alt = alt;
}
Point.prototype.update = function(i){
var range = (this.alt) ? verticalPointRange : -verticalPointRange;
this.x += pointSpeed;
this.y = (this.yStart) + Math.sin(globalTick/14) * -range;
if(this.x > (canvasWidth + spacing)){
this.x = -spacing;
var moved = points.splice(i, 1);
points.unshift(moved[0]);
}
}
var updatePoints = function(){
var i = points.length;
while(i--){
points[i].update(i);
}
}
for(iPoints = 0; iPoints < pointCount; iPoints++){
var alt = (iPoints % 2 === 0);
var offset = (alt) ? verticalPointRange : -verticalPointRange;
points.push(new Point(spacing * (iPoints-1), canvasHeight/2, alt));
}
var renderPath = function(){
context.beginPath();
context.moveTo(points[0].x, points[0].y);
for(iPath = 1; iPath < pointCount; iPath++){
context.lineTo(points[iPath].x, points[iPath].y);
}
context.stroke();
}
var loop = function(){
requestAnimationFrame(loop, canvas);
clear();
updatePoints();
renderPath();
globalTick++;
};
loop();
canvas { display: block; }
body{
background-color: ivory;
}
<canvas id="canvas"></canvas>
Canvases are transparent by default.
Try setting a page background image, and then put a canvas over it. If nothing is drawn on the canvas, you can fully see the page background.
you should try
context.clearRect(0,0,width,height);
for more you can refer How do I make a transparent canvas in html5?

Fade an image in

I am trying to fade an image in a canvas environment. Essientially what I want to do is while moving an image from left to right, I want to fade it from 0% alpha to 100% alpha. When I comment the globalAlpha and alpha info out in my code, it moves like I want it to, my only issue is getting it to fade. I am able to get the globalAlpha function to work, but it affects all the artwork in the canvas area. Is there a way I can just affect the one element? eventually I will want to fade in multiple elements at different times in the animation based on a timer, but if I can get this to work first I can go from there.
window.addEventListener('load', eventWindowLoaded, false);
function eventWindowLoaded()
{
canvasApp();
}
function canvasSupport ()
{
return Modernizr.canvas;
}
function canvasApp()
{
if (!canvasSupport())
{
return;
}
var pointImage = new Image();
pointImage.src = "images/barry.png";
var barry = new Image();
barry.src = "images/barry.png";
/*var alpha = 0;
context.globalAlpha = 1;*/
function drawScreen()
{
//context.globalAlpha = 1;
context.fillStyle = '#EEEEEE';
context.fillRect(0, 0, theCanvas.width, theCanvas.height);
//context.globalAlpha = alpha;
//Box
context.strokeStyle = '#000000';
context.strokeRect(1, 1, theCanvas.width-2, theCanvas.height-2);
if (moves > 0 )
{
moves--;
ball.x += xunits;
ball.y += yunits;
}
context.drawImage(barry, ball.x, ball.y);
/*context.restore();
alpha += .1;
if (alpha > 1)
{
alpha = 0;
}*/
}
var speed = 1;
var p1 = {x:20,y:250};
var p2 = {x:40,y:250};
var dx = p2.x - p1.x;
var dy = p2.y - p1.y;
var distance = Math.sqrt(dx*dx + dy*dy);
var moves = distance/speed;
var xunits = (p2.x - p1.x)/moves;
var yunits = (p2.y - p1.y)/moves;
var ball = {x:p1.x, y:p1.y};
var points = new Array();
theCanvas = document.getElementById("canvas");
context = theCanvas.getContext("2d");
ctx = theCanvas.getContext("2d");
setInterval(drawScreen, 10);
}
any suggestions are welcome!
I think this other question will give you a way to do so.
it shows how to load an element in the canvas context then how to fade it in..
How to change the opacity (alpha, transparency) of an element in a canvas element after it has been drawn?

Categories

Resources