I'm doing one of them stop-shot-scroll-controlled-playback sites like Sony's Be Moved.
The problem that I'm facing, considering the stop-shot technique, is the time that it takes for image to be rasterized before browser draws it on screen. It takes a lot on mobile. Probably resizing the image takes most of the cpu, but I'm not sure. This is how I show the frames:
<div
style="
position: fixed;
top:0; right:0; bottom:0; left:0;
background-image: url(...);
background-position: center;
background-size: cover;
"
></div>
The question:
Is there a way to cache a rasterized version of an image? Maybe canvas supports this? That way, when I decide to show it on screen, it'll be ready.
Right now, this is the only way I know how to cache an image.
var image = new Image();
image.src = '...';
Ref comments - there is a way to pre-cache video frames. Each frame will use a full memory block for the bitmap (which in any case also is the case with preloaded image sequences).
Cache Process
Create an "off-line" video element
Set video source with preload set to auto
You need to know the frame rate (typical: 30 fps for USA/Japan, 25 fps for Europe), calculate a time delta based on this, ie. 1 / FPS.
Use the timeupdate event for every currentTime update as setting current time is asynchronous.
Chose an in-point in the video, cache (this can take a while due to the event cycle), store to a frame buffer using a canvas element for each frame. Then playback the buffer when and as needed (this also gives you the ability to play video backwards as shown below, a feature not yet supported in the browsers).
Example
This example will load a video from net, cache 90 (3 sec # 30 fps) frames to memory, then play back the sequence ping-pong in the window (the images you see are from the cache obviously):
var canvas = document.querySelector("canvas"),
ctx = canvas.getContext("2d"),
video = document.createElement("video"),
frames = [],
w = canvas.width, h = canvas.height;
video.addEventListener("canplay", cache);
video.preload = "auto";
video.src = "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4";
function cache() {
this.removeEventListener("canplay", cache); // remove to avoid recalls
var fps = 30, // assuming 30 FPS
delta = 1 / fps, // time delta
count = 0, // current cached frame
max = fps * 3, // 3 seconds
div = document.querySelector("div"); // just for info
this.addEventListener("timeupdate", cacheFrame); // time update is aync
this.currentTime = 19; // start with initial time
function cacheFrame() {
div.innerHTML = "Caching frame: " + count;
if (count++ < max) {
// create canvas for frame-buffer;
var canvas = document.createElement("canvas"),
ctx = canvas.getContext("2d");
canvas.width = this.videoWidth; // canvas size = video frame
canvas.height = this.videoHeight;
ctx.drawImage(video, 0, 0); // draw current frame
frames.push(canvas); // store frame
this.currentTime += delta; // update time, wait..
}
else {
this.removeEventListener("timeupdate", cacheFrame); // remove!!
play(); // play back cached sequence
}
}
}
// to demo the cached frames
function play() {
var current = 0, max = frames.length, dlt = 1,
div = document.querySelector("div"),
toggle = false,
mem = max * video.videoWidth * video.videoHeight * 4; // always RGBA
mem = (mem / 1024) / 1024; //mb
ctx.fillStyle = "red";
(function loop() {
toggle = !toggle; // toggle FPS to 30 FPS
requestAnimationFrame(loop);
if (toggle) {
div.innerHTML = "Playing frame: " + current +
" (raw mem: " + mem.toFixed(1) + " mb)";
ctx.drawImage(frames[current], 0, 0, w, h); // using frame-buffer
ctx.fillRect(0, 0, current/max * w, 3);
current += dlt;
if (!current || current === max-1) dlt = -dlt; // pong-pong
}
})();
}
html, body {width:100%;height:100%}
body {margin:0; overflow:hidden;background:#aaa}
div {font:bold 20px monospace;padding:12px;color:#000}
canvas {z-index:-1;position:fixed;left:0;top:0;width:100%;height:100%;min-height:400px}
<div>Pre-loading video... wait for it, wait for it...</div>
<canvas width=600 height=360></canvas>
Canvas drawing sources are image & video objects (and some other sources which aren't relevant now). So if your unwanted delay is occurring during the initial download and rendering, then canvas will take longer because the incoming image must first be rendered onto an image object and then again rendered onto the canvas--two steps instead of one.
Your answer is not in the canvas element, so you're back to the usual solution: lessen the quantity of image bits being downloaded by lowering the quality of your images (jpg with less quality).
You could also (as you've indicated), preload & cache all your images in new Images so they can be used immediately when needed. The usual cost applies: increased memory usage for the cached images and a delay at the start of your app while all required images are downloaded.
Related
My website https://www.dimovski.nu/ starts off with a scroll-triggered animation, displaying and updating a sequence of images on scroll. I have compressed the images as much as possible to optimize load time, but it's a total of 541 images.
Is it possible to preload all the images somehow? Should I have a loading page before the trigger animation in order to avoid a choppy experience?
Here's the JS code:
const canvas = document.querySelector(".canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const context = canvas.getContext("2d");
const frameCount = 541;
const currentFrame = (index) => `./seq/${(index + 1).toString()}.jpg`;
const images = [];
let ball = {frame : 0};
for (let i = 0; i < frameCount; i++){
const img = new Image();
img.src = currentFrame(i);
images.push(img);
}
gsap.to(ball, {
frame: frameCount - 1,
snap: "frame",
ease: "none",
scrollTrigger: {
scrub: true,
pin: "canvas",
end: "500%",
},
onUpdate: render,
})
images[0].onload = render;
function render(){
context.canvas.width = images[0].width;
context.canvas.height = images[0].height;
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(images[ball.frame], 0, 0);
}
I have tried to find a solution of preloaders, but uncertain on which solution works best for my situation.
Based on the number of request required to fetch these resources and the total size I wouldn't recommend doing this. Especially not on your landing page.
You could potentially reduce number of requests by baking all of the frames into one single image as i.e. sprite sheet and show a loading spinner, but the size, without any further compression, would be similar.
Another option could be to compile the frames into a video/animation, with better compression, and have an embedded video player rendering the frames based on scroll in the borderless player.
With the code below, the quality of the video coming from my Mac's camera and shown inside <video> is great.
However the quality of the frame I capture and show in p5's canvas is pretty low, dark and grainy. Why is that and can I fix it ?
function setup() {
let canvas = createCanvas(canvasSize, canvasSize)
canvas.elt.width = canvasSize
canvas.elt.height = canvasSize
video = createCapture(VIDEO)
}
let PAUSE = false
async function draw() {
if (video && video.loadedmetadata) {
if (!PAUSE) {
// the quality of this image is much lower than what is shown inside p5's <video>
image(video.get(), 0, 0, canvasSize, canvasSize, x, y, canvasSize, canvasSize)
PAUSE = true
}
}
}
I found what the problem was.
It was not due to me setting the canvas.elt.width and canvas.elt.height, even though setting them is indeed redundant.
It's because in the code shown in the OP I was capturing the very first frame and this is too soon so the very first frame is still dark and blurry. Apparently the first few frames coming from the camera are like that.
If I give my code a delay of e.g. 5 seconds the frame it captures is then the exact same quality as the one coming from the video feed.
let video
let canvasWidth = 400
// set this to 10 on https://editor.p5js.org/ and you'll see the problem
const DELAY = 5000
function setup() {
let canvas = createCanvas(canvasWidth, canvasWidth)
canvas.elt.width = canvasWidth // redundant
canvas.elt.height = canvasWidth // redundant
video = createCapture(VIDEO)
}
let PAUSE = false
let start = Date.now()
async function draw() {
let delay = Date.now() - start
if (video && video.loadedmetadata) {
if (delay > DELAY && !PAUSE) {
PAUSE = true
let x = Math.round((video.width / 2) - (canvasWidth / 2))
let y = Math.round((video.height / 2) - (canvasWidth / 2))
// the quality of this image is now perfect
image(video.get(), 0, 0, canvasWidth, canvasWidth, x, y, canvasWidth, canvasWidth)
}
}
}
You should not be setting width/height this way. That will mess up the sizing on high DPI displays and cause your image to appear stretched and blurry.
const canvasSize = 500;
function setup() {
let canvas = createCanvas(canvasSize, canvasSize)
// Don't do this, it will mess up the sizing on high DPI displays:
// canvas.elt.width = canvasSize
// canvas.elt.height = canvasSize
video = createCapture(VIDEO)
}
let PAUSE = false;
function draw() {
if (video && video.loadedmetadata) {
if (!PAUSE) {
// the quality of this image is much lower than what is shown inside p5's <video>
image(video.get(), 0, 0, canvasSize, canvasSize, 0, 0, canvasSize, canvasSize)
}
}
}
function keyPressed() {
if (key === 'p') {
PAUSE = !PAUSE;
}
}
With this code I paused the video that was being rendered to p5.js and then took a screenshot. The version of the video displayed on the p5.js canvas wash indistinguishable from the live video.
I've made a simple setup, getting the webcam / phone camera stream and the passing it on , drawing on a html 2d canvas.
But ive been having trouble figuring out how to show the stream with a delay of few seconds. Kinda like a delay mirror.
I tried playing with ctx.globalAlpha = 0.005; but this gives me a ghosting effect rather than 'delaying' the stream.
Any idea how this can be achieved?
The snippet below doesnt work here probably because of security issues apparently but here's a pen:
https://codepen.io/farisk/pen/LvmGGQ
var width = 0, height = 0;
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');
document.body.appendChild(canvas);
var video = document.createElement('video'),
track;
video.setAttribute('autoplay',true);
window.vid = video;
function getWebcam(){
navigator.mediaDevices.getUserMedia({ video: true }).then(function(stream) {
var videoTracks = stream.getVideoTracks();
var newStream = new MediaStream(stream.getVideoTracks());
video.srcObject = newStream;
video.play();
track = stream.getTracks()[0];
}, function(e) {
console.error('Rejected!', e);
});
}
getWebcam();
var rotation = 0,
loopFrame,
centerX,
centerY,
twoPI = Math.PI * 2;
function loop(){
loopFrame = requestAnimationFrame(loop);
// ctx.globalAlpha = 0.005;
ctx.drawImage(video, 0, 0, width, height);
ctx.restore();
}
function startLoop(){
loopFrame = requestAnimationFrame(loop);
}
video.addEventListener('loadedmetadata',function(){
width = canvas.width = video.videoWidth;
height = canvas.height = video.videoHeight;
centerX = width / 2;
centerY = height / 2;
startLoop();
});
canvas.addEventListener('click',function(){
if ( track ) {
if ( track.stop ) { track.stop(); }
track = null;
} else {
getWebcam();
}
});
video,
canvas {
max-width: 100%;
height: auto;
}
The snippet below doesnt work here probably because of security issues apparently but here's a pen:
https://codepen.io/farisk/pen/LvmGGQ
You might want to consider storing the video data you get in an array of sorts. It might mean delaying the playback for n seconds at first.
Basically on frame 1, you store the video feed into an array, and draw nothing. This happened until frame 1000 (1 second). At that point start drawing based on the first element of the array.
Once you draw that frame, remove it from the array and add the new frame.
I am trying to animate using a sprite sheet and use it in my website, however I am knew to animation and I am having some trouble fixing it, currently nothing shows up on my webpage when I use this. I am using a sprite sheet which is called Speech.png, is 18176 x 256 and each frame is 256x256. Here is my code so far.
<script language="javascript">
// screen size variables
var SCREEN_WIDTH = window.innerWidth,
SCREEN_HEIGHT = window.innerHeight;
var canvas = document.createElement('canvas');
var c = canvas.getContext('2d');
canvas.width = SCREEN_WIDTH;
canvas.height = SCREEN_HEIGHT;
var xpos=0,
ypos=0,
index=0,
numFrames = 70,
frameSize= 255;
// Add our drawing canvas
document.body.appendChild(canvas);
//load the image
image = new Image();
image.src = "Speech.png";
image.onload = function() {
//we're ready for the loop
setInterval(loop, 1000 / 30);
}
function loop() {
//clear the canvas!
c.clearRect(0,0, SCREEN_HEIGHT,SCREEN_WIDTH);
/*our big long list of arguments below equates to:
1: our image source
2 - 5: the rectangle in the source image of what we want to draw
6 - 9: the rectangle of our canvas that we are drawing into
the area of the source image we are drawing from will change each time loop() is called.
the rectangle of our canvas that we are drawing into however, will not.
tricky!
*/
c.drawImage(image,xpos,ypos,frameSize,frameSize,0,0,frameSize, frameSize);
//each time around we add the frame size to our xpos, moving along the source image
xpos += frameSize;
//increase the index so we know which frame of our animation we are currently on
index += 1;
//if our index is higher than our total number of frames, we're at the end and better start over
if (index >= numFrames) {
xpos = 0;
ypos = 0;
index = 0;
//if we've gotten to the limit of our source image's width, we need to move down one row of frames
} else if (xpos + frameSize > image.width){
xpos =0;
ypos += frameSize;
}
}
</script>
I'm on Windows7 IE9 running in IE8. This works in IE9 only because I can use canvas however in IE8 it's suppose to fall back to flash canvas. Here is my source http://blog.jackadam.net/2010/alpha-jpegs/ NOW it seems im having a problem in IE with the context.drawImage not drawing the image? I've posted on the flashcanvas google group but they seem to take some time to repsond so was hoping there maybe a flashcanvas guru here.
;(function() {
var create_alpha_jpeg = function(img) {
var alpha_path = img.getAttribute('data-alpha-src')
if(!alpha_path) return
// Hide the original un-alpha'd
img.style.visiblity = 'hidden'
// Preload the un-alpha'd image
var image = document.createElement('img')
image.src = img.src + '?' + Math.random()
console.log(image.src);
image.onload = function () {
console.log('image.onload');
// Then preload alpha mask
var alpha = document.createElement('img')
alpha.src = alpha_path + '?' + Math.random()
alpha.onload = function () {
console.log('alpha.onload');
var canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
img.parentNode.replaceChild(canvas, img)
// For IE7/8
if(typeof FlashCanvas != 'undefined') FlashCanvas.initElement(canvas)
// Canvas compositing code
var context = canvas.getContext('2d')
context.clearRect(0, 0, image.width, image.height)
context.drawImage(image, 0, 0, image.width, image.height)
//context.globalCompositeOperation = 'xor'
//context.drawImage(alpha, 0, 0, image.width, image.height)
}
}
}
// Apply this technique to every image on the page once DOM is ready
// (I just placed it at the bottom of the page for brevity)
var imgs = document.getElementsByTagName('img')
for(var i = 0; i < imgs.length; i++)
create_alpha_jpeg(imgs[i])
})();
The solution to this was it only works with the older version of flashcanvas AND it only works with flashcanvas pro... as noted in the second footer of the website
This technique uses the globalCompositeOperation operation, which
requires FlashCanvas Pro. Free for non-profit use or just $31 for a
commercial license.
I got it working with flashcanvaspro. Depending which flash player it targets the max image size varies.
Flash Player 10 increased the maximum size of a bitmap to a maximum pixel count of 16,777,215.
Flash Player 9 limits (2880 x 2880 pixels).
https://helpx.adobe.com/flash-player/kb/size-limits-swf-bitmap-files.html
Need someone to update flashcanvas to the latest flash player