Fabricjs: Sprite sheet to horizontal continuous video thumbnail on canvas - javascript

I have multiple sprite sheets, one for each minute of the video. For example if video is 3 min long I have sprite.0.jpg, sprite.1.jpg, sprite.2.jpg which mean 3 sprite sheets each having 60 images for every 1 sec.
That means sprite sheet with 10 columns and 6 rows having single image size as 142x80 whereas total sprite sheet as 1420x 480 (see below)
I want to design component to show preview of continous video thumbnail on canvas. (see below)
How to convert Sprite sheets to horizontal continuous video thumbnail on fabricjs canvas?
I tried drawing the first frame of the sprite sheet on canvas but not sure how to put every sprite sheets in continous manner so that canvas is horizontally scrollable.
// Get the canvas and canvas context
var canvas = document.getElementById("timeline");
var ctx = canvas.getContext("2d");
// Create an image object
var image = new Image();
image.src = "path/to/spritesheet.jpg";
// Wait for the image to load
image.onload = function() {
// Set the canvas size
canvas.width = 1420;
canvas.height = 480;
// Draw the first frame of the sprite sheet
ctx.drawImage(image, 0, 0, 1420, 480, 0, 0, canvas.width, canvas.height);
};

I'm just going to focus on the part of your question where you split the sprite sheet into a horizontal continuous thumbnail, there are other concerns that I address after the code sample.
To do what you ask we need a loop on the sample sprite sheet you provide there are 6 rows so we loop on that and draw the rows into one.
You are using drawImage with all the parameters, I'm assuming you have the basics clear, if not see more about that in the documentation:
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage#parameters
Here is a working sample:
var canvas = document.getElementById("timeline");
var ctx = canvas.getContext("2d");
var image = new Image();
image.src = "https://i.stack.imgur.com/gOk5s.jpg";
image.onload = function() {
for (let i = 0; i < 6; i++)
ctx.drawImage(
image,
0, 80 * i, 1420, 80,
710 * i, 0, 710, 40
);
};
canvas {
border: dashed 5px red
}
<canvas id="timeline" width=4260 height=40></canvas>
I do see in your comments you added:
... what about when I want see preview of 1 hour long video? This doesn't seems right as there is limit on canvas width I guess.
Yes there is a limit and is per browser:
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas#maximum_canvas_size
But That is a much more complex issue that deserves a separate question...
one of the options could be combining the sprite tiles, instead of showing thumbnails every 1 second show every 5 or 10 to better accommodate for the large videos
another option could be to use multiple canvases

Related

Fetch Urls and merge into single high quality image (Javascript)

I've currently got a piece of JS that allows me to fetch all the IMG urls inside a certain DIV (vpc-preview). I'm now looking for a solution to take these Urls and merge them into one image that pops up for the user to download.
I'm using this method rather than something like the "HTMLCanvasElement.toDataURL" method as I want a high quality image output. I found the Html2canvas method generates a blurry image especially on retina. The original URL's are high quality images which would be ideal if just merged.
Note: The images will not have consistent names and there can be anywhere between 5-30 images stacked on top of eachother. Which is why I'm collecting them from the container they reside in as that and the height/width are the only constants.
Below is a Fiddle I've put together to show what I have so far, the only difference is the images will be stacked on top of each other rather than side by side.
Demo Fiddle
function img_find() {
var imgs = document.querySelectorAll('#vpc-preview img');
var imgSrcs = [];
for (var i = 0; i < imgs.length; i++) {
imgSrcs.push(imgs[i].src);
}
return alert(imgSrcs);
}
UPDATE: The following fiddle lets users upload images and then merges them into a single photo for download. Unfortunately my coding skills aren't good enough to manipulate this into using the SRC urls of images within certain DIV and to Merge all the photos on top of each other. Click here to see thread this came from.
Fiddle
function addToCanvas(img) {
// resize canvas to fit the image
// height should be the max width of the images added, since we rotate -90 degree
// width is just a sum of all images' height
canvas.height = max(lastHeight, img.width);
canvas.width = lastWidth + img.height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (lastImage) {
ctx.drawImage(lastImage, 0, canvas.height - lastImage.height);
}
ctx.rotate(270 * Math.PI / 180); // rotate the canvas to the specified degrees
ctx.drawImage(img, -canvas.height, lastWidth);
lastImage = new Image();
lastImage.src = canvas.toDataURL();
lastWidth += img.height;
lastHeight = canvas.height;
imagesLoaded += 1;
}

Big image drawn on small canvas become blurrier

In my project i have a canvas (200*150) and i want to draw an image of size (800*600) . The result is that image become bluer (not smooth and clear) , but if we put that image on img tag it look well. So how can we deal with this? thanks.
<img src="http://www.drivingkids.com/wp-content/uploads/2010/07/preschool-math-game-for-kids-math-racing-equatations.jpg"
width="200" heigh="150" />
<canvas id="my_canvas" width="200" height="150"></canvas>
<script type="text/javascript">
window.onload = function () {
var context = document.getElementById("my_canvas").getContext("2d");
var image = new Image();
image.src = "http://www.drivingkids.com/wp-content/uploads/2010/07/preschool-math-game-for-kids-math-racing-equatations.jpg";
image.onload = function () {
context.drawImage(image, 0, 0, context.canvas.width, context.canvas.height); //dx-30, GY-28, GW+50, GH+35
}
}
</script>
Cause
Browsers can prioritize quality or performance depending on the current scenario.
For images quality is typically prioritized over performance for most pages. But for canvas performance is not as good as with for instance direct browser rendering and CSS so the interpolation with canvas may have performance prioritized before quality. Depending on browser implementation.
Solution
Luckily there is a way to work around this by sort of splitting the burden with the resizing and interpolation by doing it in two (or more) steps, or one intermediate step if you like.
The intermediate step will first scale the image 50% to an off-screen canvas. Then use that canvas to draw to the final size. For larger images more steps will perhaps be needed.
The time spent in sum is about the same due to the sum of the operations to get the new sizes (more simply put: less to interpolate with intermediate step x2, versus more to interpolate x1) so you won't notice much performance reduction.
But most importantly: the result will be better than with just a single step.
Implementation
This is how you can implement an intermediate step:
image.onload = function () {
/// create an extra step for re-sizing image
var tmpCanvas = document.createElement('canvas'),
tmpContext = c.getContext('2d');
/// set this canvas to 50% of image
tmpCanvas.width = image.width * 0.5;
tmpCanvas.height = image.height * 0.5;
/// draw image step 1
tmpContext.drawImage(image, 0, 0, image.width * 0.5, image.height * 0.5);
/// draw image step 2
context.drawImage(tmpCanvas, 0, 0, canvas.width, canvas.height);
}
Demo (proof-of-concept)
ONLINE DEMO HERE
The result will be:
Left image: IMG element. Right image: canvas two steps (rendered in Firefox)
As you can see there is now no noticeable difference between image and canvas element.

swapping div with canvas elements using javascript

I have 2 div. Every div has a canvas element. At the beginning of my program I put an image on the canvas. The images are different. Then I try to swap the divs using
var contentofmyfirstdiv = $('#myfirstdiv').html();
var contentofmyseconddiv = $('#myseconddiv').html();
$('#myseconddiv').html(contentofmyfirstdiv);
$('#myfirstdiv').html(contentofmyseconddiv)
;
All works except that every canvas is empty. It seems the "contentof..." didn't get the image from the canvas... Anyone knows how to swap the content of the canvas? I am using Firefox.
You are serializing to html and reparsing it, this is lossy operation. Instead you should operate
on the live DOM tree directly:
var contentofmyfirstdiv = $('#myfirstdiv').children().detach();
var contentofmyseconddiv = $('#myseconddiv').children().detach();
$('#myseconddiv').append(contentofmyfirstdiv);
$('#myfirstdiv').append(contentofmyseconddiv)
Here's a demo that doesn't do justice but nevertheless http://jsfiddle.net/9JdqQ/
What's retained compared to serialization are unserializable attributes, event listeners, element data (canvas image etc)..
If you are only using images to draw on the canvas there is really no need for the canvas - you could just use the images directly and swap those.
However, if you need to use canvas for other reasons you can extract the data directly from one, draw the other canvas to this, and then put the data from the first to the second.
This method shows a way that doesn't need DOM manipulation.
One option using a new canvas as a swap:
var temp = document.createElement('img');
temp.width = canvas1.width;
temp.height = canvas1.height;
//swap
var tempctx = temp.getContext('2d');
tempctx.drawImage(canvas1, 0, 0);
ctx1.drawImage(canvas2, 0, 0);
ctx2.drawImage(temp, 0, 0);
The other method using an image element as a swap:
var temp = canvas1.toDataURL();
ctx1.drawImage(canvas2, 0, 0);
var img = document.createElement('img');
img.onload = function() {ctx2.drawImage(this, 0, 0);}
img.src = temp;
The latter not optimal in speed as you need to compress and encode the data-uri.
Note that this example assume the images being of the same size. If the images are of different sizes you will need to handle this as well by setting canvas1 and 2 to the correct sizes after the image is copied to swap and before you redraw.
swap = canvas1
resize canvas1 = canvas2
draw canvas2 to canvas1
resize canvas2 = swap
draw swap to canvas2

how to add a mask AND a background to canvas element

I am trying to make an image masked by a png with a background image. It works fine when I put the masked image only but if I add a background image the front image is not masked anymore
<script>
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var bg = new Image();
var front = new Image();
var mask = new Image();
bg.src = 'img/background.jpg';
bg.onload = function() {
front.src = "img/tobemasked.jpg";
};
front.onload = function() {
mask.src = "img/mask.png";
}
mask.onload = function() {
display();
}
function display() {
context.drawImage(bg, 0, 0); // it works if i remove this line
context.drawImage(tmpMask, 0, 0);
context.globalCompositeOperation = "source-in";
context.drawImage(front, 0, 0);
}
</script>
I guess this is a quite common problem but I couldn't find anything related on the web.
Thank you for your help.
The "source-in" operation you are using to mask the front image is defined here http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#gcop-source-in like this:
Display the source image wherever both the source image and
destination image are opaque. Display transparency elsewhere.
Without drawing the background image, the destination image consists of only your mask and supposedly has transparent pixels. The front image will be displayed only where the mask is opaque. I understand that this is what you want.
However, after you have also drawn the background image, there are supposedly no transparent pixels in the destination image, it's totally opaque, and when you draw the front image, all parts of it will be displayed.
This would accomplish what I think you are trying to do:
context.drawImage(mask, 0, 0);
context.globalCompositeOperation = "source-in";
context.drawImage(front, 0, 0);
context.globalCompositeOperation = "destination-atop";
context.drawImage(bg, 0, 0);
The front image is masked first. After that there still are transparent pixels. The background image gets drawn on the transparent area with "destination-atop" option (see previous link).
I think that's the simplest method. Some other options are to use multiple canvas elements on top of each others to create layers or use some javascript library that makes it possible to use layers with canvas.

canvas - layer small image on larger background image, merge to single canvas pixel data

How do I merge a smaller image on top of a larger background image on one canvas. The smaller image will move around. So I will need to keep reference of the original background pixel data, so that the each frame the canvas can be redrawn, with the overlay in its new position.
BgImage: 1280 x 400, overlayImage: 320 x 400, overlayOffsetX: mouseX
I think it is common to draw whole scene each time you want to change something, so:
context.drawImage(bgImage, 0, 0);
context.drawImage(overlayImage, overlayOffsetX, 0);
UPDATE
You could manually compose image data of two images with making copy of background image data
or
do something easier, probably faster. You could create new canvas element (without attaching to the document) which would store image data in easy to manage form. putImageData is good if you want to place rectangular image into the canvas. But if you want to put image with transparency, additional canvas will help. See if example below is satisfying you.
// preparation of canvas containing data of overlayImage
var OVERLAY_IMAGE_WIDTH = 320;
var OVERLAY_IMAGE_Height = 400;
var overlayImageCanvas = document.createElement('canvas');
overlayImageCanvas.width = OVERLAY_IMAGE_WIDTH;
overlayImageCanvas.height = OVERLAY_IMAGE_HEIGHT;
overlayImageCanvas.getContext('2d').putImageData(overlayImage, 0, 0);
// drawing scene, execute this block every time overlayOffsetX has changed
context.putImageData(bgImage, 0, 0);
context.drawImage(overlayImageCanvas, overlayOffsetX, 0);

Categories

Resources