HTML 5 Video to Canvas Scaling issues - javascript

I'm trying to capture a frame of an html5 video in order to create a thumbnail of it but I'm seeing some strange results when the image is rendered to canvas.
What I'm seeing is the image displayed in canvas is just a section of the video scaled up quite a bit! As seen in the image below:
And the code is impossibly simple as well:
$(document).ready(function(){
var $Body = $("body");
var $Video = $("<video>").appendTo($Body);
var cVideo = $Video.get(0);
cVideo.addEventListener("loadedmetadata", function(){
cVideo.addEventListener("seeked", function(){
var $Canvas = $("<canvas>").width(cVideo.videoWidth).height(cVideo.videoHeight);
$Canvas.appendTo($Body);
var cCtx = $Canvas.get(0).getContext("2d");
cCtx.drawImage(cVideo, 0, 0);
}, false);
cVideo.currentTime = 500;
}, false);
cVideo.src = "movie.mkv";
});
I have tried many combinations of widths/heights/clipping regions etc but all im able to achieve is varying version of looking at only the top right corner of the original video.
Edit: I will ad that the original video size is 1920 x 800 and in the image provided that is the size of both the video and canvas tags. I have tried them at different sizes as well and still the same result
Edit2: I have tried multiple formats/videos and OSs and still have the same problem so I don't believe the problem to be related to any codec issue for example

Okay I don't know why this makes any difference to the canvas element but for this to work I had to set the width and height of the canvas using its width/height attributes and then I could change the size of the canvas as I please and the video would fill the area of the canvas. Full solution below:
$(document).ready(function(){
var $Body = $("body");
var $Video = $("<video>").appendTo($Body);
var cVideo = $Video.get(0);
cVideo.addEventListener("loadedmetadata", function(){
cVideo.addEventListener("seeked", function(){
var $Canvas = $("<canvas>").attr("width", cVideo.videoWidth).attr("height", cVideo.videoHeight);
$Canvas.appendTo($Body);
var cCtx = $Canvas.get(0).getContext("2d");
cCtx.drawImage(cVideo, 0, 0, cVideo.videoWidth, cVideo.videoHeight);
}, false);
cVideo.currentTime = 500;
}, false);
cVideo.src = "movie.mkv";
});

Related

Android Cordova - How to draw photo taken on canvas?

i'm working with an hybrid Android app and I need to take a picture from camera, process the image and then show the results obtained on another window that should contain a small preview of the picture taken and some text with the results.
Here is the relevant code that is not currently working as I need:
From script of first window:
.
.
.
CameraPreview.takePicture(function(imgData){
var mImage = new Image();
mImage.onload = function(){
var canvas = document.createElement('canvas');
canvas.width = mImage.width;
canvas.height = mImage.height;
var context = canvas.getContext('2d');
context.drawImage(mImage, 0, 0);
var imgData = context.getImageData(0, 0, canvas.width, canvas.height);
.
.(Process imgData[])
.
localStorage.setItem("resultImage", mImage.src);
window.open('result.html','_blank','location=no');
};
mImage.src = 'data:image/jpeg;base64,'+imgData;
});
Then, "result.html" pops up, which has the following html content:
...
<body>
...
<canvas id="resultCanvas" style="position: absolute; top: 0%; left: 0%;"></canvas>
...
</body>
...
And the following javascript code:
...
var mImage = new Image();
mImage.onload = function(){
var canvas = document.getElementById('resultCanvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight*0.3; // 1/3 of phone screen size
var context = canvas.getContext('2d');
context.drawImage(mImage);
navigator.notification.alert('Height = '+mImage.height+'\n'+'Width ='+mImage.width, function(){},'Image','Done');
};
mImage.src = localStorage.getItem("resultImage");
...
The alert window shows the 640x480 of the image, but the canvas is transparent where it should contain the picture taken.
If I replace the "context.drawImage()" statement with "context.fillrect()" I can see the black rectangle drawn on screen, so it looks that the canvas is there, but somehow I can not make it draw the desired image.
Hope I explained myself correctly.
I really appreciate any help. Thanks!
Well, finally I solved it, first I tryied using an "img" element instead of a canvas, and that works fine, but the problem is I need to make some drawings over the picture, so insisting on code I posted before, I realized the problem was on the use of drawImage() method in the second script, it is necessary to specify position, so replacing:
context.drawImage(mImage);
with
context.drawImage(mImage,0,0);
solved everything.

I would like to merge all images within DIV and allow user to download (js powered)

I'm working on a client's web design and development powered by Wordpress and Woocommerce, the plugin is great but there's a few functions I wish it had. The main one is giving the ability for the user to save the preview of their configuration.
The plug-in is 'Woocommerce Visual Products Configurator':
It allows buyers to build their own item by selecting from a series of different attributes for different parts of an item. Ex, choosing different types of Soles for a shoe, as well as being able to choose from a series of laces for the same shoe.
My Issue: The configuration images are layered on top of each other as the user selects their options so any normal "save as" function will just save the top image.
I have succesfully managed to combine and save the images using html2canvas-Data URL() but the way it generates the screenshots means the quality becomes very poor.
My thoughts are to merge all the images within the "vpc-preview" DIV then force the download.
Hunting through the functions of the plugin I found this:
function merge_pictures($images, $path = false, $url = false) {
$tmp_dir = uniqid();
$upload_dir = wp_upload_dir();
$generation_path = $upload_dir["basedir"] . "/VPC";
$generation_url = $upload_dir["baseurl"] . "/VPC";
if (wp_mkdir_p($generation_path)) {
$output_file_path = $generation_path . "/$tmp_dir.png";
$output_file_url = $generation_url . "/$tmp_dir.png";
foreach ($images as $imgs) {
list($width, $height) = getimagesize($imgs);
$img = imagecreatefrompng($imgs);
imagealphablending($img, true);
imagesavealpha($img, true);
if (isset($output_img)) {
imagecopy($output_img, $img, 0, 0, 0, 0, 1000, 500);
} else {
$output_img = $img;
imagealphablending($output_img, true);
imagesavealpha($output_img, true);
imagecopymerge($output_img, $img, 10, 12, 0, 0, 0, 0, 100);
}
}
imagepng($output_img, $output_file_path);
imagedestroy($output_img);
if ($path)
return $output_file_path;
if ($url)
return $output_file_url;
} else
return false;
}
However the function isn't called anywhere. There's also a couple of "save" buttons that are commented out which makes me wonder if they removed from a previous build.
Ideally I'd like the user to be able to instantly share their creation to facebook but thought this would be a good start.
Any ideas would be greatly appreciated!
Thanks!
UPDATE:
I've managed to use the following code to output an alert of the url's of each of the config images. Useless for the user but at least I know it's targeting exactly what I need.
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);
}
Any suggestions on how to manipulate this and merge the corresponding image of each URL then force the download?
Cick to see Fiddle
UPDATE 2: 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.
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;
}
Based on this question/answer, I would take a look at node-canvas.
What I have done in the past is use a #print css file to only capture the divs needed. Very simple concept but works well.
After looking at your link, that is exactly the situations I use it in..online designers. It is a little more difficult keeping your layers responsively aligned, but in the end I feel your codes become cleaner and it works well across devices and OS'.
If you dont want PDF and a simple preview and download than html2image.js is perfect.
$(document).ready(function(){
var element = $("#html-content-holder"); // global variable
var getCanvas; // global variable
$("#btn-Preview-Image").on('click', function () {
html2canvas(element, {
onrendered: function (canvas) {
$("#previewImage").append(canvas);
getCanvas = canvas;
}
});
});
$("#btn-Convert-Html2Image").on('click', function () {
var imgageData = getCanvas.toDataURL("image/png");
// Now browser starts downloading it instead of just showing it
var newData = imgageData.replace(/^data:image\/png/, "data:application/octet-stream");
$("#btn-Convert-Html2Image").attr("download", "your_pic_name.png").attr("href", newData);
});
});
<script src="https://github.com/niklasvh/html2canvas/releases/download/0.4.1/html2canvas.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="html-content-holder" style="background-color: #F0F0F1; color: #00cc65; width: 500px;
padding-left: 25px; padding-top: 10px;">
Place any code here
</div>
<input id="btn-Preview-Image" type="button" value="Preview"/>
<a id="btn-Convert-Html2Image" href="#">Download</a>
<br/>
<h3>Preview :</h3>
<div id="previewImage">
</div>
For High quality you want to use rasterizeHTML.js
Found here:
https://github.com/cburgmer/rasterizeHTML.js/blob/master/examples/retina.html

Firefox drawing blank Canvas when restoring from saved base64

I am working with a single canvas that allows the user to click on a window pane in a window image. The idea is to show where the user has clicked. The image will then be modified (by drawing a grill on the window) and then saved to in JPEG. I am saving the canvas image prior to the click function because I don't want the selection box to show in the final image. However, Firefox often displays a blank canvas when restoring the canvas where IE and Chrome do not. This works perfectly in Chrome and IE. Any suggestions? Does Firefox have a problem with toDataURL()? Maybe some async issue going on here? I am also aware that saving a canvas in this fashion is memory intensive and there may be a better way to do this but I'm working with what I have.
Code:
/**
* Restores canvas from drawingView.canvasRestorePoint if there are any restores saved
*/
restoreCanvas:function()
{
var inverseScale = (1/drawingView.scaleFactor);
var canvas = document.getElementById("drawPop.canvasOne");
var c = canvas.getContext("2d");
if (drawingView.canvasRestorePoint[0]!=null)
{
c.clearRect(0,0,canvas.width,canvas.height);
var img = new Image();
img.src = drawingView.canvasRestorePoint.pop();
c.scale(inverseScale,inverseScale);
c.drawImage(img, 0, 0);
c.scale(drawingView.scaleFactor, drawingView.scaleFactor);
}
},
/**
* Pushes canvas into drawingView.canvasRestorePoint
*/
saveCanvas:function()
{
var canvas = document.getElementById("drawPop.canvasOne");
var urlData = canvas.toDataURL();
drawingView.canvasRestorePoint.push(urlData);
},
EXAMPLE OF USE:
readGrillInputs:function()
{
var glassNum = ir.get("drawPop.grillGlassNum").value;
var panelNum = ir.get("drawPop.grillPanelNum").value;
drawingView.restoreCanvas();
drawEngine.drawGrill(glassNum, panelNum,null);
drawingView.saveCanvas();
},
sortClick:function(event)
{
..... //Sorts where user has clicked and generates panel/glass num
.....
drawingView.showClick(panelNum, glassNum);
},
showClick:function(panelNum, glassNum)
{
var glass = item.panels[panelNum].glasses[glassNum];
var c = drawEngine.context;
drawingView.restoreCanvas();
drawingView.saveCanvas();
c.strokeStyle = "red";
c.strokeRect(glass.x, glass.y, glass.w, glass.h);
},
By just looking at the code setting the img.src is an async action to retrieve the image, so when you try to draw it 2 lines later to the canvas, it probably hasn't been loaded yet (having it in cache will make it return fast enough that it might work).
You should instead use an img.onload function to draw the image when it has loaded.
restoreCanvas:function()
{
var inverseScale = (1/drawingView.scaleFactor);
var canvas = document.getElementById("drawPop.canvasOne");
var c = canvas.getContext("2d");
if (drawingView.canvasRestorePoint[0]!=null)
{
c.clearRect(0,0,canvas.width,canvas.height);
var img = new Image();
img.onload = function() {
c.scale(inverseScale,inverseScale);
c.drawImage(img, 0, 0);
c.scale(drawingView.scaleFactor, drawingView.scaleFactor);
};
img.src = drawingView.canvasRestorePoint.pop();
}
},

Force constant dimensions of a HTML video element regardless of dimensions and ratio of source

I am trying to do a not so simple task. I would like to have in a HTML5 page a video element with constant width and height (those of the window), that can manages the dimensions and aspect ratio of the source video to display at best that is to say with the window fully covered with the video and with no scroll bars.
I wrote this javascript code:
$("video").bind("loadedmetadata", function () {
var screenSize = {}, videoSize = {};
videoSize["width"] = this.videoWidth;
videoSize["height"] = this.videoHeight;
screenSize["height"] = $( window ).height();
screenSize["width"] = $( window ).width();
var ratio_screen = screenSize["width"]/screenSize["height"];
var ratio_video = videoSize["width"]/videoSize["height"];
if (ratio_video > ratio_screen) {
$("video").height(screenSize["height"]);
$("video").width(screenSize["height"]*ratio_screen);
}
else
{
$("video").width(screenSize["width"]);
$("video").height(screenSize["width"]/ratio_screen);
}
});
At the moment, I have a video element, almost fitting the window (I still have a border or margin that inspector says to be part of html element). But the source video is fitting inside the video element! As an example for my test video which is wider than the screen, I have a black strip over and under the video.
How can I manage this to "zoom" the video. Do I have to apply a scaling factor to the video element or somenthing can be done at source video level.
Thanks
Finally this piece of code worked:
$("video").on('canplay',function() {
$("video").bind("loadedmetadata", function () {
var screenSize = {}, videoSize = {};
videoSize["width"] = this.videoWidth;
videoSize["height"] = this.videoHeight;
screenSize["height"] = $( window ).height();
screenSize["width"] = $( window ).width();
var ratio_screen = screenSize["width"]/screenSize["height"];
var ratio_video = videoSize["width"]/videoSize["height"];
if (ratio_video > ratio_screen) {
$("video").css("-webkit-transform", "scale("+ratio_video/ratio_screen+")");
$("video").height(screenSize["height"]);
$("video").width(screenSize["height"]*ratio_screen);
} else {
$("video").css("-webkit-transform", "scale("+ratio_video/ratio_screen+")");
$("video").width(screenSize["width"]);
$("video").height(screenSize["width"]/ratio_screen);
}
});
});
This results in displaying a video, filling the whole screen, without stretching the video if the aspect ratio is not fitting the one of the screen.
Note that my video element plays several sources in a loop. The first video played does not apply this resizing function (I have to work on that to ensure the at start up this works).

jQuery and Canvas loading behaviour

After being a long time lurker, this is my first post here! I've been RTFMing and searching everywhere for an answer to this question to no avail. I will try to be as informative as I can, hope you could help me.
This code is for my personal webpage.
I am trying to implement some sort of a modern click-map using HTML5 and jQuery.
In the website you would see the main image and a hidden canvas with the same size at the same coordinates with this picture drawn into it.
When the mouse hovers the main picture, it read the mouse pixel data (array of r,g,b,alpha) from the image drawn onto the canvas. When it sees the pixel color is black (in my case I only check the RED value, which in a black pixel would be 0) it knows the activate the relevant button.
(Originally, I got the idea from this article)
The reason I chose this method, is for the page to be responsive and dynamically change to fit different monitors and mobile devices. To achieve this, I call the DrawCanvas function every time the screen is re-sized, to redraw the canvas with the new dimensions.
Generally, this works OK. The thing is ,there seems to be an inconsistent behavior in Chrome and IE(9). When I initially open the page, I sometimes get no pixel data (0,0,0,0), until i re-size the browser. At first I figured there's some loading issues that are making this happen so I tried to hack it with setTimeout, it still doesn't work. I also tried to trigger the re-size event and call the drawCanvas function at document.ready, still didn't work.
What's bothering me is most, are the inconsistencies. Sometimes it works, sometimes is doesn't. Generally, it is more stable in chrome than in IE(9).
Here is the deprecated code:
<script type="text/javascript">
$(document).ready(function(){setTimeout(function() {
// Get main image object
var mapWrapper = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
// Create a hidden canvas the same size as the main image and append it to main div
var canvas = document.createElement('canvas');
canvas.height = mapWrapper.clientHeight;
canvas.width = mapWrapper.clientWidth;
canvas.fillStyle = 'rgb(255,255,255)';
canvas.style.display = 'none';
canvas.id = 'hiddencvs';
$('#map_wrapper').append(canvas);
// Draw the buttons image into the canvas
drawCanvas(null);
$("#map_wrapper").mousemove(function(e){
var canvas = document.getElementById('hiddencvs');
var context = canvas.getContext('2d');
var pos = findPos(this);
var x = e.pageX - pos.x;
var y = e.pageY - pos.y;
// Get pixel information array (red, green, blue, alpha)
var pixel = context.getImageData(x,y,1,1).data;
var red = pixel[0];
var main_img = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
if (red == 0)
{
...
}
else {
...
}
});
},3000);}); // End DOM Ready
function drawCanvas(e)
{
// Get context of hidden convas and set size according to main image
var cvs = document.getElementById('hiddencvs');
var ctx = cvs.getContext('2d');
var mapWrapper = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
cvs.width = mapWrapper.clientWidth;
cvs.height = mapWrapper.clientHeight;
// Create img element for buttons image
var img = document.createElement("img");
img.src = "img/main-page-buttons.png";
// Draw buttons image inside hidden canvas, strech it to canvas size
ctx.drawImage(img, 0, 0,cvs.width,cvs.height);
}
$(window).resize(function(e){
drawCanvas(e);
}
);
function findPos(obj)
{
...
}
</script>
I'd appreciate any help!
Thanks!
Ron.
You don't wait for the image to be loaded so, depending on the cache, you may draw an image or not in the canvas.
You should do this :
$(function(){
var img = document.createElement("img");
img.onload = function() {
var mapWrapper = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
...
// your whole code here !
...
}
img.src = "img/main-page-buttons.png";
});

Categories

Resources