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" />
Related
I am trying to merge multiple frames from a GIF to convert them into spritesheet. I am somehow able to extract frames using libgif.js
here is my code. The Canvas in which i want to put all my images is empty and is at the end i dont know what is wrong with it.
<img hidden src="https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif" rel:auto_play="0"
rel:rubbable="0" />
<div id="frames">
</div>
<canvas id="myCanvas" style="border:1px solid #d3d3d3;">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://unpkg.com/jsgif#1.0.2/libgif.js"></script>
<script>
$(document).ready(function () {
$('img').each(function (idx, img_tag) {
var total = 0;
if (/^.+\.gif$/.test($(img_tag).prop("src"))) {
var rub = new SuperGif({ gif: img_tag, progressbar_height: 0 });
rub.load(function () {
for (var i = 0; i < rub.get_length(); i++) {
total += 1;
rub.move_to(i);
var canvas = cloneCanvas(rub.get_canvas());
$("#frames").append('<img id = "' + i + '"src= "' + canvas + '">');
}
for (var i = 0; i <= total; i++) {
id = i.toString();
var img = document.getElementById(id);
window.onload = function () {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.drawImage(img, 10, 10);
}
}
});
}
});
});
function cloneCanvas(oldCanvas) {
var img = oldCanvas.toDataURL("image/png");
return img;
}
</script>
The main issue in your solution is that you are looping through all inserted frame-images and using window.onload with the expectation to draw images on the canvas. However, you are assigning windows.onload on the image elements after the images have already been attached to the dom in the previous (for i = 0) iteration, through $("#frames").append(.... This means that your onload handlers are getting registered after the event has already fired.
I rearranged your code into the snippet below to make sure that onload is registered before appending the image-frames into the document.
In this solution here I created the "spritesheet" by putting the image-frames vertically one-after-the-other. Tweak the solution accordingly to create your desired "spritesheet layout".
The tricky part is that you need to set a width and height for the canvas in a dynamic fashion, which means you have to wait for all image-frames to be loaded into the document and meassure their collective widths and heights.
The algorithm should be:
Collect all widths and heights of image-frames onload
Calculate the total canvas.width and canvas.height depending on
your desired layout (in my vertical-one-ofter-the-other solution
this means to use the maximal image width as canvas width and the
sum of all image heights as height. If you need a different
spritesheet layout then you need to adapt this logic differently)
Draw all the images on the canvas, which by this point has enough
width and height to fit all your gif-frame-images.
In my solution I did not implement this algorith, I rather hard-coded the canvas width and height after manually calculating the max-img-width and total-img-height for your particular gif example.
$(document).ready(function () {
const c = document.getElementById("myCanvas");
const ctx = c.getContext("2d");
let canvasWidth = 0;
let canvasHeight = 0;
$('img').each(function (idx, img_tag) {
var total = 0;
if (/^.+\.gif$/.test($(img_tag).prop("src"))) {
var rub = new SuperGif({ gif: img_tag, progressbar_height: 0 });
rub.load(function () {
for (let i = 0; i < rub.get_length(); i++) {
total += 1;
rub.move_to(i);
var canvas = cloneCanvas(rub.get_canvas());
const img = $('<img id = "' + i + '"src= "' + canvas + '">');
img.on('load', function() {
// draw the image
ctx.drawImage(this, 0, canvasHeight);
// extend canvas width, if newly loaded image exceeds current width
canvasWidth = $(this).width() > canvasWidth ? $(this).width() : canvasWidth;
// add canvas height by the newly loade image height value
canvasHeight += $(this).height();
// resize sprite-canvas to host the new image
canvas.width = canvasWidth;
canvas.height = canvasHeight;
});
$("#frames").append(img);
}
});
}
});
});
function cloneCanvas(oldCanvas) {
var img = oldCanvas.toDataURL("image/png");
return img;
}
Gif:
<img hidden src="https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif" rel:auto_play="0"
rel:rubbable="0" />
<hr />
Frames:
<div id="frames">
</div>
<hr />
Canvas:
<hr />
<canvas id="myCanvas" style="border:1px solid #d3d3d3;" width="400" height="17600">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://unpkg.com/jsgif#1.0.2/libgif.js"></script>
I am building an application where I need to be able to capture a frame-grab from a web camera and upload it to the application. To do this, I am using WebRTC to capture the image from the video stream, write it to a canvas, and then grab the data and upload it. This works fine for the most part. However, I needed to give the user a way to adjust the brightness. To accomplish this, I added a slider to the page with the video stream on it, that applies "style.webkitFilter = 'brightness(X%)'" when adjusted. Again, this works fine, but did not adjust the brightness of the captured image, just the displayed video stream. The javascript function I use to actually capture the image is:
function takepicture() {
var context = canvas.getContext('2d');
var b = document.getElementById('txtBrightness').value;
if (width && height) {
canvas.width = width;
canvas.height = height;
context.filter = 'brightness(' + b + '%)';
context.drawImage(video, 0, 0, width, height);
var data = canvas.toDataURL();
//photo.setAttribute('src', data);
//alert(data);
document.getElementById('Image').value = data;
//$("#Image").val(data);
$("#form1").submit();
} else {
clearphoto();
}
}
This works in Chrome, Firefox, and the Chromium based version of Edge, however, it does NOT work in the EdgeHTML versions of Edge. In those versions of Edge, the video stream is correctly displayed with the adjusted brightness, but the captured image does not have the brightness adjusted. Is there a workaround to make context filters work with the EdgeHTML versions of the Edge browser?
From the CanvasRenderingContext2D.filter, it seems that CanvasRenderingContext2D.filter not support Microsoft Edge browser (EdgeHtml version).
As a workaround, you could try to use Konva plugin to apply filter to an image.
Sample code as below:
<head>
<meta charset="utf-8" />
<title></title>
<script src="https://unpkg.com/konva#4.1.3/konva.min.js"></script>
<meta charset="utf-8" />
<title>Konva Brighten Image Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #f0f0f0;
}
#slider {
position: absolute;
top: 20px;
left: 20px;
}
</style>
</head>
<body>
<div id="container"></div>
<input id="slider" type="range" min="-1" max="1" step="0.05" value="0" />
<script>
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
for (var src in sources) {
numImages++;
}
for (var src in sources) {
images[src] = new Image();
images[src].onload = function () {
if (++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
function buildStage(images) {
var stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
});
var layer = new Konva.Layer();
var lion = new Konva.Image({
image: images.lion,
x: 80,
y: 30,
draggable: true
});
lion.cache();
lion.filters([Konva.Filters.Brighten]);
layer.add(lion);
stage.add(layer);
var slider = document.getElementById('slider');
slider.oninput = function () {
lion.brightness(slider.value);
layer.batchDraw();
};
}
var sources = {
lion: '/images/lion.png'
};
loadImages(sources, buildStage);
</script>
</body>
The output like this:
Requirement:
Users can type in text that goes as an overlay on top of an image.
Users can download the image with the overlay.
To do this, I'm drawing the image onto the canvas, filling with the text and then setting the link's href to the canvas's dataURL.
Findings:
I can see the text overlay on the image in the canvas just fine.
If I right-click on the canvas and download the image, I can see the text overlay just fine.
If I click on the link, I see the original image without the text overlay.
What did I miss?
Here's the snippet:
var imgURL = 'https://upload.wikimedia.org/wikipedia/commons/5/56/Neptune_Full.jpg';
function loadCanvas(dataURL) {
var canGreeting = document.getElementById('canGreeting');
var context = canGreeting.getContext('2d');
// load image from data url
var imageObj = new Image();
imageObj.crossOrigin = 'anonymous';
imageObj.onload = function() {
context.drawImage(imageObj, 0, 0);
context.font = "20px sans-serif";
context.fillStyle = "#FFFFFF";
var arrayOfLines = $('#txtGreetingText').val().split('\n');
var y = 50;
var i = 0;
$(arrayOfLines).each(function() {
context.fillText(arrayOfLines[i], 50, y);
i++;
y += 30;
});
};
imageObj.src = dataURL;
lnkDownload.download = "card.jpg";
lnkDownload.href = imageObj.src;
}
$(document).ready(function() {
loadCanvas(imgURL);
$("#txtGreetingText").on("keydown", function(e) {
loadCanvas(imgURL);
});
});
textarea {
width: 420px;
height: 200px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<textarea name="txtGreetingText" id="txtGreetingText"></textarea>
<br/>
<canvas id="canGreeting" width="480" height="480"></canvas>
<br/>
<a id="lnkDownload">Download this card</a>
href attribute should be pointing to base64 encoded image of the canvas source. Do this:
$(arrayOfLines).each(function() {
context.fillText(arrayOfLines[i], 50, y);
i++;
y += 30;
});
// udpate link to image
// Grab base64 encoded URL
var url = canGreeting.toDataURL("image/png;base64;");
lnkDownload.href = url;
I have the following fiddle here: http://jsfiddle.net/tbt0jspd/2/
The idea is to display a series of boxes on the screen using the box image that will have a class of box on them but ONE will have a class of win and the rest will have a class of lose.
This is how I handle adding the boxes to the canvas element:
var boxes = 6;
var boxImg = new Image();
boxImg.src = 'img/box.png';
boxImg.onload = function() {
for (var i = 0, x = 20, y = 200; i < boxes; x += 148, y = 200, i++) {
context.drawImage(boxImg, x, y);
}
};
The first question is how do I add the classes as described above to the images? They should all have a class of box, and 5 should have a class of lose, and 1 a class of win.
Users will then click these boxes and a class of opened will be applied to them in turn. The boxes will change to a different image depending on their class of win or lose.
$('.box').not('.opened').on('click', function() {
if( $(this).hasClass('win') ) {
$(this).src = 'img/box-win.png';
} else if( $(this).hasClass('lose') ) {
$(this).src = 'img/box-lose.png';
}
$(this).addClass('opened');
if( $('.opened').length == boxes )
{
alert('all boxes open');
}
});
You can't have classes on boxes that or on canvas, because after you draw an image on canvas, it becomes nothing more but a set of pixels.
What you can do, however, is modify which image gets drawn on the canvas based on its class (or another property), but you'll have to redraw it on the canvas whenever it's changed.
Redrawing them on canvas is the key.
For easier control, you might consider using an object to represent boxes (not just their images), so you can easily toggle their states/images/etc.
UPDATE
Here is a simple example using the button to open all the boxes and assign them win/lose state randomly: http://jsfiddle.net/d7ov9pak/
var boxes = 6;
var canvas = document.getElementById('OpenTheBox');
var context = canvas.getContext('2d');
var boxImg = new Image();
boxImg.src = 'http://i59.tinypic.com/vqp4c7.png';
var winImg = new Image();
winImg.src = 'http://i60.tinypic.com/2ujtr0i.png';
var loseImg = new Image();
loseImg.src = 'http://i60.tinypic.com/oivsc0.png';
var cwidth = canvas.width = window.screen.width;
var cheight = canvas.height = window.screen.height;
var ctop = canvas.offsetTop;
var cleft = canvas.offsetLeft;
context.fillStyle = '#000000';
context.fillRect(0, 0, cwidth, cheight);
canvas.oncontextmenu = function (e) {
e.preventDefault();
};
boxImg.onload = function () {
for (var i = 0, x = 20, y = 200; i < boxes; x += 148, y = 200, i++) {
context.drawImage(boxImg, x, y);
}
};
function openBoxes() {
// clear the canvas, just in case
canvas.width = canvas.width;
for (var i = 0, x = 20, y = 200; i < boxes; x += 148, y = 200, i++) {
// assign a random win/lose box
var image = (Math.random() >= 0.5) ? winImg : loseImg;
context.drawImage(image, x, y);
}
}
* {
margin: 0;
padding: 0;
border: 0;
outline: 0;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
background: #000000;
}
<button onclick="openBoxes()">Open the boxes</button>
<canvas id="OpenTheBox"></canvas>
Note, if this is all there is to it and you don't need any other canvas functionality, perhaps you should consider not using the canvas, and simply manipulate DOM element to show different images based on CSS classes.
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.