Creating a linear gradient overlay on <canvas> HTML5 <video> - javascript

background
I have a looping video of some time-lapsed clouds downloadable here. I would like to add a gradient over top of the clouds in real time.
I would put live link to code however, video cannot be streamed played with via canvas due to security. so please download video if you want a sample (or your own)
The end result (I made in Photoshop):
In the end, I'll be generating the colors of this gradient at run time.
question
How do I create a linear gradient over my canvas that will alter the video?
code
html:
<canvas id="c"></canvas>
<video autoplay loop id="v">
<source src='images/cloud.mp4' type='video/mp4' />
</video>
css:
#c {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 100%;
height: 100%;
}
javascript:
document.addEventListener('DOMContentLoaded', function(){
var v = document.getElementById('v');
var canvas = document.getElementById('c');
var context = canvas.getContext('2d');
var back = document.createElement('canvas');
var backcontext = back.getContext('2d');
var cw,ch;
v.addEventListener('play', function(){
cw = v.clientWidth;
ch = v.clientHeight;
canvas.width = cw;
canvas.height = ch;
back.width = cw;
back.height = ch;
draw(v,context,backcontext,cw,ch);
},false);
},false);
function draw(v,c,bc,w,h) {
if(v.paused || v.ended) return false;
// First, draw it into the backing canvas
bc.drawImage(v,0,0,w,h);
// Grab the pixel data from the backing canvas
var idata = bc.getImageData(0,0,w,h);
var data = idata.data;
// Currently this loops through the pixels, turning them grayscale as a proof of concept. I believe this is where the gradient + blending modes would go.
for(var i = 0; i < data.length; i+=4) {
var r = data[i];
var g = data[i+1];
var b = data[i+2];
var brightness = (3*r+4*g+b)>>>3;
data[i] = brightness;
data[i+1] = brightness;
data[i+2] = brightness;
}
idata.data = data;
// Draw the pixels onto the visible canvas
c.putImageData(idata,0,0);
// Start over!
setTimeout(function(){ draw(v,c,bc,w,h); }, 0);
}
notes
The colors are easier in hsla and I would like to change the Hue at run time.
Colors:
----HSLA(268,92%,19%,.5);
----HSLA(30,100%,50%,.5);
The gradient has a blending mode of Overlay.

Related

Javascript color picker. How can I display all 255x255x255 RGB values without making the browser crash?

So I'm not talking about the HTML element I want to make a JS color picker.
So far I created a HTML Canvas and added each RGB value possible and that failled since, well printing all 24bits is quite intensive.But I am confused how do I create an RGBA color picker in JS?
Surley all examples I've seen are not displaying all the RGB spectrum, how do they work?
Is the an algorithm or way to display a RGB spectrum picker and be light on the browser?
Thanks.
Here's something to play with. A few things to note.
I've leveraged the browser's ability to understand the hsl colour system to avoid writing a conversion between it and rgb.
It's one-way. something-in --> rgb-out. You can't convert rgb to hsl so conveniently.
redrawing on mousemove isn't what you'd want. You'd want to handle clicks on each canvas.
I just used 360x100 since there are 360 deg in a circle and 100x100 since it avoid computing a percentage. Recalling that you need an input in the range of 0-360 and two inputs in the range 0-100..
"use strict";
window.addEventListener('load', onLoaded, false);
function onLoaded(evt)
{
let can = document.querySelector('canvas');
let ctx = can.getContext('2d');
for (let x=0; x<360; x++)
{
ctx.fillStyle = `hsl(${x}deg, 100%, 50%)`;
ctx.fillRect(x,0,1,can.height);
}
can.addEventListener('mousemove', onMouseMove, false);
}
function onMouseMove(evt)
{
let can = this;
let xPos = evt.offsetX;
let yPos = evt.offsetY;
let ctx = this.getContext('2d');
let imgData = ctx.getImageData(xPos,yPos,1,1);
let r = imgData.data[0];
let g = imgData.data[1];
let b = imgData.data[2];
let tgt = document.getElementById('hovCol');
tgt.style.backgroundColor = `rgb(${r},${g},${b})`;
drawSatLumCanvas(xPos);
}
function drawSatLumCanvas(hueDegrees)
{
let can = document.querySelectorAll('canvas')[1];
let ctx = can.getContext('2d');
for (var y=0; y<100; y++)
{
for (var x=0; x<100; x++)
{
ctx.fillStyle = `hsl(${hueDegrees}deg, ${x}%, ${100-y}%)`;
ctx.fillRect(x,y,1,1);
}
}
}
.swatch
{
display: inline-block;
height: 1em;
width: 1em;
border: solid 1px #888;
}
<body>
<canvas width=360 height=100></canvas><br>
Hovered: <span class='swatch' id='hovCol'></span><br>
<canvas width=100 height=100 id='slCol'><canvas>
</body>

How can I animate a gradient with large pixels?

I am working on a project where I would like to have darkness covering the screen and the character glowing in the darkness. I tried to animate the scene then draw darkness over it using this code:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var pixelSize = 30;
var width = canvasWidth/pixelSize;
var height = canvasHeight/pixelSize;
var lightX = canvasWidth/2;
var lightY = canvasHeight/2;
var lightDiameter = 100;
var a = lightDiameter*pixelSize;
for(var x = 0; x < width; x++) {
for(var y = 0; y < height; y++) {
var alpha = 1.25 - a/(Math.pow(x*30 - lightX, 2) + Math.pow(y*30 -
lightY, 2));
ctx.fillStyle = "rgba( 25, 25, 30," + alpha + ")";
ctx.fillRect(x*pixelSize, y*pixelSize, pixelSize, pixelSize);
}
}
This worked pretty well and I liked the way it looked, but when this was repeatedly animated alongside the other code it slowed the rest down significantly. I think a possible solution may be to somehow draw a gradient with a lower "quality?", another solution I have considered is to save this drawing in a separate canvas and drawing it translated to the players location but that would make it impossible to add multiple sources of light, which I would like to do by simply adding their effect. I may just have to deal with the lag and I'm a noob at this stuff, but if anyone can help me that would be wonderful.
To clarify, I am using this code in the drawing loop, and also it is re-calculated in every iteration. I would prefer to recalculate this way so I can have multiple moving sources of light.
This is because fillRect is pretty slow compared to other methods. You could probably speed things up by using ImageData objects instead.
The way to do this would be to render everything to the canvas, get the corresponding ImageData, modify its contents and put it back onto the canvas:
var ctx = canvas.getContext("2d");
// render stuff here
var imageData = ctx.getImageData(0,0,canvasWidth,canvasHeight);
for (let x=0;x<canvasWidth;x++){
for (let y=0;y<canvasHeight;y++){
let i = (x+y*canvasWidth)*4;
let alpha = calculateAlpha(x,y); // your method here (should result in a value between 0 and 1)
imageData.data[i] = (1-alpha)*imageData.data[i]+alpha*25;
imageData.data[i+1] = (1-alpha)*imageData.data[i+1]+alpha*25;
imageData.data[i+2] = (1-alpha)*imageData.data[i+2]+alpha*30;
imageData.data[i+3] = 1-(1-alpha)*(1-imageData.data[i+3]);
}
}
ctx.putImageData(imageData,0,0);
This should do the lighting on a per-pixel basis, and much faster than using clearRect all the time. However, it might still slow things down, as you're doing a lot of calculations each frame. In that case, you could speed thing up by doing the lighting in a second canvas that is positioned over your main canvas using css:
<div id="container">
<canvas id="canvas"></canvas>
<canvas id="lightingCanvas"></canvas>
</div>
Css:
#container {
position: relative;
}
#canvas, #lightingCanvas {
position: absolute;
top: 0;
left: 0;
}
#container, #canvas, #lightingCanvas {
width: 480px;
height: 360px;
}
Javascript:
var canvas = document.getElementById("lightingCanvas")
var ctx = canvas.getContext("2d");
ctx.fillStyle = "rgb(25,25,30)";
ctx.fillRect(0,0,canvas.width,canvas.height);
var imageData = ctx.getImageData(0,0,canvasWidth,canvasHeight);
for (let x=0;x<canvasWidth;x++){
for (let y=0;y<canvasHeight;y++){
let i = (x+y*canvasWidth)*4;
let alpha = calculateAlpha(x,y); // your method here (should result in a value between 0 and 1)
imageData.data[i+3] = 255*alpha;
}
}
ctx.putImageData(imageData,0,0);
This way the browser takes care of the blending for you and you just need to plug in the correct alpha values - so rendering should be even faster now.
This will also allow you to bring the large pixels back in - just use a lower resolution on the second canvas and use some css effect like image-rendering: -webkit-crisp-edges to make the canvas pixelated when scaled up.

How to drawImage behind all other content on a canvas? [duplicate]

This question already has an answer here:
HTML Canvas: Drawing grid below a plot
(1 answer)
Closed 6 years ago.
I have a canvas, and I want to use drawImage to draw an image behind the current content on the canvas.
Due to the fact that there is content already on the canvas (I'm using Literally Canvas to create a canvas containing an image, so I can't really draw the image first), I cannot use drawImage before I render the rest of my content.
Is it possible to drawImage behind all other content on a canvas?
Yes you can just use globalCompositeOperation destination-over, but note that your first image needs some transparency, otherwise, you will obviously not see anything :
var img1 = new Image();
var img2 = new Image();
var loaded = 0;
var imageLoad = function(){
if(++loaded == 2){
draw();
}
};
img1.onload = img2.onload = imageLoad;
var draw = function(){
var ctx = c.getContext('2d');
ctx.drawImage(img1, 100,100);
// wait a little bit before drawing the background image
setTimeout(function(){
ctx.globalCompositeOperation = 'destination-over';
ctx.drawImage(img2, 0,0);
}, 500);
}
img1.src = "https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png";
img2.src = "https://picsum.photos/200/200";
<canvas id="c" width="200" height="200"></canvas>
Sorry about the previous post, I didn't properly read your post
Perhaps you could save the canvas, draw your image, and then reload the old content on top of your drawn image? Here's some JS psuedocode:
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
ctx.drawImage('Your Image Watermark Stuff');
ctx.putImageData(imgData,0,0);
You can use KonvaJS. And then use layers for it.
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.rawgit.com/konvajs/konva/0.13.0/konva.min.js"></script>
<meta charset="utf-8">
<title>Konva Rect Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #F0F0F0;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Konva.Layer();
var imageObj = new Image();
imageObj.onload = function() {
var baseImage = new Konva.Image({
x: 50,
y: 50,
width: width,
height: height,
image: image
});
// add the shape to the layer
layer.add(rect);
// add the layer to the stage
stage.add(layer);
};
imageObj.src = 'url to your image'
</script>
</body>
</html>
A simple solution would be to use another canvas behind the first one.
Normally canvas pixels are initialized to transparent black and therefore are perfectly see-through.
If your first canvas is created opaque instead the only other option I can think to is
create a temporary canvas of the same size
draw your image in this temporary canvas
get the ImageData object of both the temporary canvas and of the original canvas
copy from the temporary canvas to the original canvas only where the original canvas is not set at the background color
In code:
var tmpcanvas = document.createElement("canvas");
tmpcanvas.width = canvas.width;
tmpcanvas.height = canvas.height;
var temp_ctx = tmpcanvas.getContext("2d");
// ... draw your image into temporary context ...
var temp_idata = temp_ctx.getImageData(0, 0, canvas.width, canvas.height);
var temp_data = temp_idata.data;
// Access the original canvas pixels
var ctx = canvas.getContext("2d");
var idata = ctx.getImageData(0, 0, canvas.width, canvas.height);
var data = idata.data;
// Find the background color (here I'll use first top-left pixel)
var br_r = data[0], bg_g = data[1], bg_b = data[2];
// Replace all background pixels with pixels from temp image
for (var i=0,n=canvas.width*canvas.height*4; i<n; i+=4) {
if (data[i] == bg_r && data[i+1] == bg_g && data[i+2] == bg_b) {
data[i] = tmp_data[i];
data[i+1] = tmp_data[i+1];
data[i+2] = tmp_data[i+2];
data[i+3] = tmp_data[i+3];
}
}
// Update the canvas
ctx.putImageData(idata, 0, 0);
this approach however will have a lower quality if the original canvas graphics has been drawn with antialiasing or if pixels of the background color are also used in the image (e.g. an object on #FFF white background where object highlights are also #FFF). Another problem is if the background color is not a perfectly uniform RGB value (this will happen if the image has been compressed with a lossy algorithm like jpeg).
All these problems could be mitigated with more sophisticated algorithms like range matching, morphological adjustments and color-to-alpha conversions (basically the same machinery used for chroma-keying).

Draw video frame-by-frame on html5 canvas and control with slider

I am creating a walkthrough video where the user can slide a UI slider across the screen, and the camera will walk through through a 3D space.
The video has been exported as jpg frames, and numbered 0 to 350.jpg.
I am pre-loading all of the frames first, and then applying the function to the slider change.
This is the canvas
<canvas id="walkthrough" width="1280" height="300"></canvas>
This is the function from the jQuery UI slider applying data.value
$("[data-slider]")
.each(function () {
var input = $(this);
$("<span>")
.addClass("output")
.insertAfter($(this));
})
.bind("slider:ready slider:changed", function (event, data) {
$(this)
.nextAll(".output:first")
.html(data.value.toFixed(3));
});
This is the image prelaod function
var totalImages = 50; // Wow, so many images for such a short clip
var images = new Array();
for(var i = 1; i < totalImages; i++) {
var filename = '/walkthrough/' + i + '.jpg'; // Filename of each image
var img = new Image;
img.src = filename;
images.push(img);
}
This is the function that should draw the image to canvas when the slider is changed
$("#my-input").bind("slider:changed", function (event, data) {
var currentimage = '/walkthrough/' + data.value + '.jpg';
var canvas = document.getElementById("walkthrough");
var context = canvas.getContext("2d");
context.drawImage(currentimage,10,10);
});
I have tried to adapt this code from an article I read here which is using the scroll position to draw the image instead of a data.value.
http://elikirk.com/canvas-based-scroll-controlled-backgroud-video-use-parallax-style-web-design/
I appreciate any help with this!
Here's a demo that uses a slider to change the image drawn on a canvas. Some notable differences from your code:
uses the native HTML5 slider instead of jQuery UI
use the input event instead of the change event to detect slider changes
access the slider value with event.target.value instead of data (which isn't defined on the input event)
use the slider value as an index into the array of pre-loaded images instead of a file path
var canvas = document.getElementById("canvas");
canvas.height = 150;
canvas.width = 400;
var totalImages = 72;
var videoFrames = [];
for (var i = 1; i <= totalImages; i++) {
var videoFrame = new Image;
var videoFrameUrl = 'http://rphv.net/walkthrough/tmp-' + i + '.gif';
videoFrame.src = videoFrameUrl;
videoFrames.push(videoFrame);
}
$("#my-input").on("input", function(event) {
var currentImage = videoFrames[event.target.value - 1];
var context = canvas.getContext("2d");
context.drawImage(currentImage, 0, 0);
});
html,
body {
height: 100%;
margin: 0 auto;
}
canvas {
height: 150px;
width: 400px;
border: 1px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas"></canvas>
<br>
<input id="my-input" type="range" min="1" max="72" value="1" />

Text and animation together on HTML5 canvas

I have an animation based canvas that on mouseover animates rain droplets and the animation stops on mouseout. I have textbox which on submit should show text on canvas. However this text disappears when i moveout and mouseover again. I know that the canvas is redrawn on mouseover but i am unable to figure how to make the text remain where it is. Thanks!
I have adapted the code from the solution provided here =>
Random images falling like rain in canvas (Javascript)
Javascript
var ctx;
var imgBg;
var imgDrops;
var x = 0;
var y = 0;
var noOfDrops = 7;
var fallingDrops = [];
var intV;
imgBg = new Image();
imgBg.src = "image.jpg";
var canvas = document.getElementById('canvasRegn');
ctx = canvas.getContext('2d');
ctx.drawImage(imgBg,0,0,600,450); //Background
function draw() {
ctx.drawImage(imgBg, 0, 0,600,450); //Background
for (var i=0; i< noOfDrops; i++)
{
ctx.drawImage (fallingDrops[i].image, fallingDrops[i].x, fallingDrops[i].y); //The rain drop
fallingDrops[i].y += fallingDrops[i].speed;
fallingDrops[i+4].x += fallingDrops[i].speed-1;//Set the falling speed
if (fallingDrops[i].y > 450) { //Repeat the raindrop when it falls out of view
fallingDrops[i].y = -120; //Account for the image size
fallingDrops[i].x = Math.random() * 600; //Make it appear randomly along the width
}
}
}
function setup() {
intV = setInterval(function(){draw()}, 36);
for (var i = 0; i < noOfDrops; i++) {
var fallingDr = new Object();
fallingDr["image"] = new Image();
fallingDr.image.src = "Rain.svg";
fallingDr["x"] = Math.random() * 600;
fallingDr["y"] = Math.random() * 5;
fallingDr["speed"] = 3 + Math.random() * 5;
fallingDrops.push(fallingDr);
}
}
function start(){
setup();
}
function stop(){
clearInterval(intV);
}
function clicked(){
var x=document.getElementById("form_val");
ctx.clearRect(0,0,600,400);
ctx.font="36px Verdana";
ctx.fillStyle="yellow";
ctx.strokeStyle="green";
ctx.lineWidth=2;
ctx.strokeText(x.value,200,200);
ctx.fillText(x.value,200,200);
}
HTML
<canvas id="canvasRegn" width="600" height="450"style="margin:10px;" onmouseover="start()" onmouseout="stop()">
</canvas>
<br>
<input type="text" name="fname" size="50" id="form_val">
<button id="submit" onclick="clicked()">Submit</button>
Each time you redraw the canvas you need to redraw the textbox, I would personally rename "clicked()" and call it from inside "draw()" (either before or after the drops depending on whether you want it to appear above or below)
You'd also have to remove the ctx.clearRect() from "clicked()" or it will overwrite the rain (if you're placing it on top)
Then you'd need to edit how it was called, the clicked() function could set a boolean variable which is checked inside the draw function (and if true, draws the textbox)
Pseudo code example:
var text = false
draw(){
drawRain()
if(text == true){drawText()}
}
clicked(){
text = true
}
Then if you wanted the textbox to be editable, you can use variables instead of fixed values in the drawText() e.g.
Outside the drawText()
fontVar = "36px Verdana";
fillColour = "yellow";
strokeColour = "green";
Inside the drawText()
ctx.font=fontVar;
ctx.fillStyle=fillColour;
ctx.strokeStyle=strokeColour;
The answer to this question lies in the laying of two canvas layers. First Canvas layer will have the background image and the animation effect. Second layer on top of it will draw the Text.
Note : Credit to #DBS for finding the solution.
JavaScript:
script type="text/javascript">
var ctx;
var ctx2
var imgBg;
var imgDrops;
var x = 0;
var y = 0;
var noOfDrops = 20;
var fallingDrops = [];
var intV;
fontVar ="36px Verdana";
fillColour="yellow";
strokeColour="green";
imgBg = new Image();
imgBg.src = "image.jpg";
var canvas = document.getElementById('myCanvas');
ctx = canvas.getContext('2d');
ctx.drawImage(imgBg,0,0,600,400); //Background
var canvas2 = document.getElementById('drawText');
ctx2 = canvas2.getContext('2d');
function drawing(){
ctx.drawImage(imgBg,0,0,600,400); //Background
}
function draw() {
ctx.drawImage(imgBg, 0, 0,600,400); //Background
for (var i=0; i< noOfDrops; i++)
{
ctx.drawImage (fallingDrops[i].image, fallingDrops[i].x, fallingDrops[i].y,35,35); //The rain drop
fallingDrops[i].y += fallingDrops[i].speed;
fallingDrops[i+7].x += fallingDrops[i].speed-1;//Set the falling speed
if (fallingDrops[i].y > 400) { //Repeat the raindrop when it falls out of view
fallingDrops[i].y = -120; //Account for the image size
fallingDrops[i].x = Math.random() * 600; //Make it appear randomly along the width
}
}
}
function setup() {
intV = setInterval(function(){draw()}, 36);
for (var i = 0; i < noOfDrops; i++) {
var fallingDr = new Object();
fallingDr["image"] = new Image();
fallingDr.image.src = "Rain.svg";
fallingDr["x"] = Math.random() * 600;
fallingDr["y"] = Math.random() * 5;
fallingDr["speed"] = 3 + Math.random() * 5;
fallingDrops.push(fallingDr);
}
}
function start(){
setup();
}
function stop(){
clearInterval(intV);
}
function clicked(){
z=document.getElementById("form_val");
ctx2.clearRect(0,0,600,400);
ctx2.font=fontVar;
ctx2.fillStyle=fillColour;
ctx2.strokeStyle=strokeColour;
ctx2.lineWidth=2;
ctx2.strokeText(z.value,200,200);
ctx2.fillText(z.value,200,200);
}
</script>
HTML
<div class="wrapper">
<canvas id="myCanvas" width="600" height="400" style="margin:1px;"></canvas>
<canvas id="drawText" width="600" height="400" onmouseover="start()" onmouseout="stop()</canvas>
</div>
<br>
Greeting Message: <input type="text" name="fname" size="50" id="form_val">
<button id="submit" onclick="clicked()">Add this message</button>
</div>
CSS
How can I stack two same-sized canvas on top of each other?

Categories

Resources