I'm trying to capture an image in the browser using html2canvas. Capturing an image of the whole browser works. But I need to specify x,y start and end coordinates that I want to capture. In the docs I saw that html2canvas can accept x,y coordinates:
x: Default: Element x-offset Description: Crop canvas x-coordinate
y: Default: Element y-offset Description: Crop canvas y-coordinate
Passing my x,y coordinates to those parameters just captures the whole window.
So instead, I tried capturing the whole window, and then cropping an area from it using drawImage() (found at some other stackoverflow post, not sure which):
function snapImage(x1,y1,x2,y2, e){
html2canvas(document.body).then(function(canvas) {
// calc the size -- but no larger than the html2canvas size!
var width = Math.min(canvas.width,Math.abs(x2-x1));
var height = Math.min(canvas.height,Math.abs(y2-y1));
// create a new avatarCanvas with the specified size
var avatarCanvas = document.createElement('canvas');
avatarCanvas.width=width;
avatarCanvas.height=height;
avatarCanvas.id = 'avatarCanvas';
// put avatarCanvas into document body
document.body.appendChild(avatarCanvas);
// use the clipping version of drawImage to draw
// a clipped portion of html2canvas's canvas onto avatarCanvas
var avatarCtx = avatarCanvas.getContext('2d');
avatarCtx.drawImage(canvas,x1,y1,width,height,0,0,width,height);
});
}
This draws a shifted image with a wrong offset. For example, given the following website:
image taken from the example at: https://github.com/niklasvh/html2canvas/tree/master/examples
I mark "pluot?" area to snap it:
see the dotted rectangle
The dotted rectangle is drawn using js, given the mouse coordinates in 2 events: onmousedown and onmouseup. Because the rectangle is drawn correctly, I assume my coordinates are correct. But when I pass these coordinates to the function snapImage() above, I get the following captured image:
Looks like there's an offset. Maybe the start coordinates drawImage() operates on differ from my canvas start coordinates?
EDIT:
Turns out that my code works when I'm on 100% zoom. It doesn't though when I zoom in / out.
I guess this is because you get x and y from event with clientX and clientY. Use pageX and pageY instead. Have a look at this jsFiddle
let startX, startY;
document.getElementsByTagName('body')[0].addEventListener('mousedown', function(event) {
console.log("ok");
startX = Math.floor(event.pageX);
startY = Math.floor(event.pageY);
});
document.getElementsByTagName('body')[0].addEventListener('mouseup', function(event) {
snapImage(Math.min(event.pageX, startX), Math.min(event.pageY, startY), Math.max(event.pageX, startX), Math.max(event.pageY, startY));
});
function snapImage(x1,y1,x2,y2, e){
console.log(x1, x2, y1, y2);
html2canvas(document.body).then(function(canvas) {
// calc the size -- but no larger than the html2canvas size!
var width = Math.min(canvas.width,Math.abs(x2-x1));
var height = Math.min(canvas.height,Math.abs(y2-y1));
// create a new avatarCanvas with the specified size
var avatarCanvas = document.createElement('canvas');
avatarCanvas.width=width;
avatarCanvas.height=height;
avatarCanvas.id = 'avatarCanvas';
// put avatarCanvas into document body
document.body.appendChild(avatarCanvas);
// use the clipping version of drawImage to draw
// a clipped portion of html2canvas's canvas onto avatarCanvas
var avatarCtx = avatarCanvas.getContext('2d');
avatarCtx.drawImage(canvas,x1,y1,width,height,0,0,width,height);
});
}
Turns out there's a built-in chrome function captureVisibleTab that captures the image of the active tab. So I ended up using that instead of html2canvas. I got help from the Copyfish Chrome Extension. Github code here: Copyfish.
Here's my code:
Listener:
//listener in background.js which invokes the screen capture
chrome.tabs.captureVisibleTab(function (dataURL) {
sendResponse({
dataURL: dataURL,
});
});
Receiver:
//receiver in content.js which gets the captured image and crops it accordingly
function(response){
var img = new Image();
img.src = response.dataURL;
var dpf = window.innerWidth / img.width;
var scaleFactor = 1 / dpf,
sx = Math.min(x1, x2) * scaleFactor,
sy = Math.min(y1, y2) * scaleFactor,
width = Math.abs(x2 - x1),
height = Math.abs(y2 - y1);
// create a new avatarCanvas with the specified size
var avatarCanvas = document.createElement('canvas');
avatarCanvas.width = width;
avatarCanvas.height = height;
avatarCanvas.id = 'avatarCanvas';
// put avatarCanvas into document body
document.body.appendChild(avatarCanvas);
// use the clipping version of drawImage to draw
var avatarCtx = avatarCanvas.getContext('2d');
avatarCtx.drawImage(img, sx, sy, scaledWidth, scaledHeight, 0, 0, width, height);
}
x,y coordinates are taken by e.clientX and e.clientY respectively.
This method is zoom- and resolution- proof.
Related
I have been asked to get the mouse coordinates in a game made in html5 with canvas.
As a first test, try reading the mouse position with the function below. But this function only reads the mouse position taking into account the dimensions of the canvas.
What happens is that the game has a larger stage than the canvas and this function does not show me the real location of the character on the stage.
I was doing a search and noticed that "behind" the canvas exists on a map (.png) with pixel dimensions already established. The canvas works like the camera to see a portion of the map.
Will it be possible to adapt my function to read the dimensions of the map and then locate the actual coordinates of the player?
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext("2d");
canvas.addEventListener("click", function(e) {
var cRect = canvas.getBoundingClientRect();
var scaleX = canvas.width / cRect.width;
var scaleY = canvas.height / cRect.height;
var canvasX = Math.round((e.clientX - cRect.left) * scaleX);
var canvasY = Math.round((e.clientY - cRect.top) * scaleY);
console.log("X: "+canvasX+", Y: "+canvasY);
});
This function will only give me the position of the mouse based on the size of the canvas but the map is larger, I leave here an explanatory image.
I hope you have understood me. Thanks in advance.
World <=> View
To establish the vernacular, the terms used are
World: the coordinate system (in pixels) of world / playfield / (red box).
View: The coordinate system (in canvas pixels) of canvas / camera / (blue box).
As pointed out in the comments. You need the view origin. That is the coordinates that the top left of the canvas in world space.
You also need to know the view scale. That is the size of the canvas in relationship to the world.
Required information
const world = {width: 2048, height: 1024}; // Red box in pixels
const view = { // blue box
origin: {x: 500, y: 20}, // in world scale (pixels on world)
scale: {width: 1, height: 1}, // scale of pixels (from view to world)
}
Without this information you can not do the conversion. It must exist as it is required to render world content to the canvas.
Note that if the scales are 1 they may only be inferred in the canvas rendering system. If you can not find a scale then use 1.
Note This answer assumes there is no rotation of the view.
View => World
The following function will convert from view coordinates to world coordinates.
function viewToWorld(x, y) { // x,y pixel coordinates on canvas
return {
x: x * view.scale.width + view.origin.x,
y: y * view.scale.height + view.origin.y
}; // return x,y pixel coordinates in world
}
To use in a mouse event where the client is the canvas
function mouseEvent(event) {
// get world (red box) coords
const worldCoord = viewToWorld(event.clientX, event.clientY);
// normalize
worldCoord.x /= world.width;
worldCoord.y /= world.height;
}
World => View
You can reverse the conversion. That is move from world coordinates to view coordinates with the following functions.
function normalWorldToView(x, y) { // x,y normalized world coordinates
return {
x: (x * world.width - view.origin.x) / view.scale.width,
y: (y * world.height - view.origin.y) / view.scale.height
}; // return x,y pixel on canvas (view)
}
and in pixels
function worldToView(x, y) { // x,y world coordinates in pixels
return {
x: (x - view.origin.x) / view.scale.width,
y: (y - view.origin.y) / view.scale.height
}; // return x,y pixel on canvas (view)
}
I'm trying to make a line graph using the canvas that looks like a typical line graph and uses typical Cartesian coordinates like we learned in algebra;
starts with 0,0 at the bottom left, and the position x-axis is to be determined by the number of items to chart.
However, the position of the points doesn't match the input (although the shape of the graph is correct, indicating I'm doing something right). What am I doing wrong?
I've rewritten and tweaked the formula for converting numerous times
function newLineGraph(parent, width, height, dataArray) {
//this makes the element using my own code, no observable error here
var canvas = newCanvas(parent, width, height);
var canvasContext = canvas.getContext("2d");
var spaceBetweenEntries = width / dataArray.length;
var largestNumber = findHighestNumber(dataArray);
canvasContext.beginPath();
canvasContext.moveTo(0, 0);
var n = 0;
while (dataArray[n]) {
var x = spaceBetweenEntries * n;
var y = height - dataArray[n];
console.log("x,y", x, y);
canvasContext.lineTo(x, y);
n++;
}
canvasContext.stroke();
return canvas;
}
edit: fixed the image so you can see the canvas size
The resulting graph is much smaller than the intended graph; for example
newLineGraph("body",55,45,[1,40,10]);
produces a graph with a small ^ shape in the corner, rather than properly starting at the bottom. However, the console logs show " 0 44" "18.333333333333332 5","36.666666666666664 35" which I believe should produce a graph that fits the whole chart nicely.
The first lineTo will always have x as 0 so I assume the first line isn't drawing like you intended. It is more like a |/\ shape instead of \/\.
Set x like this:
var x = spaceBetweenEntries * (n + 1);
Edit
As you can see in this fiddle your chart renders at the right points with the coordinates you posted. I implemented the newCanvas function like I expect it to behave. So are we missing some other code that modifies the canvas width and height?
function newCanvas(parent, width, height) {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
document.querySelector(parent).appendChild(canvas);
return canvas;
}
The problem was using style.width and style.height to modify the canvas height, instead of canvas.height and canvas.width
So I have a function that dynamically produces a cropped section of a world map. The map has various points plotted onto it, plotted by longitude and latitude, depending on the data passed into the function from elsewhere in the script. (Don't worry about how these values are calculated, just accept they are calculated where I have put [number] in my code). I've worked out how to crop my map dynamically, but what I'm noticing is that there is a lot of transparent whitespace to the right of the image after the crop, when the image is appended to a div on the page. How do I remove this whitespace?
Please note that each crop will be of a different size. Setting overflow:hidden property on the containing div and limiting the containing div to a precise pixel width will not achieve what I want to achieve.
Thx u
-- Gaweyne
createZoomedMapImage: function(imageURL){
var imageObj = new Image();
imageObj.onload = _.bind(function(){
var canvas = document.createElement("canvas"),
context = canvas.getContext("2d"),
$canvas = $(canvas),
w = imageObj.width,
h = imageObj.height;
canvas.width = w;
canvas.height = h;
var startingX = [number]
var starting Y = [number]
var deltaWidth = [number]
deltaHeight = [number]
context.drawImage(imageObj, startingX, startingY, deltaWidth, deltaHeight, 0, 0, (deltaWidth*2), (deltaHeight*2));
var zoomedImage = canvas.toDataURL('image/png');
}, this);
imageObj.src = imageURL;
}
jsfiddle.net/Gaweyne/r0t3hoo6
The image tag looks like what is displayed in the result. I have an image of, say, 300 x 600px. But the actual graphic only takes up 300 x 300 pixels. I don't want the graphic to take up the full width of the image. I want the image to be 300 x 300 pixels. I don't want to set this explicitly with CSS because the cropped maps will differ in size depending on the data.
Try to use:
canvas.width = deltaWidth;
canvas.height = deltaHeight;
context.drawImage(imageObj, startingX, startingY, deltaWidth, deltaHeight, 0, 0, (deltaWidth*2), (deltaHeight*2));
var zoomedImage = canvas.toDataURL('image/png');
I have a canvas in the center of a website. When I perform a mouse click on the canvas, I want a small image to be drawn at the click-location. In manage to get the correct coordinates of a canvas click I structute a JavaScript-function like this:
function click( event ) {
var ctxt;
var myX = event.clientX;
var myY = event.clientY;
myX-=canvas.offsetTop;
myY-=canvas.offsetLeft;
ctxt = canvas.getContext("2d");
ctxt.drawImage(myImage, myX, myY);
alert(myX + " " + myY);
}
The alert function shows the correct coordinates, but the image is drawn at a location with much higher coordinate-values. If I click a little bit to far down or to the left, the image is not drawn at all (probably because its outside the canvas).
The drawn image has a x-coordinate that's about 3 times as high as the x-coordinate of the click, and the y-coordinate is about 5 times as high.
What can be the problem?
Hank
You probably forgot to define a size for the canvas bitmap, and is only using CSS to set the size. Remember that canvas size must set implicit as CSS does not affect its bitmap size.
<canvas id="myCanvas" width=500 height=500></canvas>
If not your bitmap which defaults to 300x150 will be stretched to whatever you set with CSS which means your coordinates will be scaled as well.
CSS should be skipped for this but if you absolutely want to use it set width and height in CSS to the same size as defined for your canvas element.
The mouse position you get will be relative to the window so you need to subtract the canvas position to make it relative to canvas element. You probably have this working already and iHank's example should work, although I would not obtain the context each time:
var canvas = document.getElementById('myCanvas'),
ctx = canvas.getContext('2d');
canvas.addEventListener('click', mouseClick, false);
ctx.strokeRect(0, 0, canvas.width, canvas.height);
function mouseClick(e) {
var rect = canvas.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top;
// draw image here, for demo - drawn from corner not center:
ctx.fillRect(x, y, 5, 5);
}
Canvas: <canvas id="myCanvas" width=500 height=180></canvas>
Seems like I missed that the default size of a canvas was (300, 150). If I change the width and height of the canvas-object to the sizes specified in the cs-file, it works!
Try:
function click( event ) {
var ctxt;
var myX = event.clientX;
var myY = event.clientY;
offsetXY = canvas.getBoundingClientRect();
myX-=offsetXY.top;
myY-=offsetXY.left;
ctxt = canvas.getContext("2d");
ctxt.drawImage(myImage, myX, myY);
alert(myX + " " + myY);
}
"The returned value is a TextRectangle object, which contains read-only left, top, right and bottom properties describing the border-box in pixels. top and left are relative to the top-left of the viewport." https://developer.mozilla.org/en-US/docs/Web/API/Element.getBoundingClientRect
Hope that's what you needed.
EDIT: offsetXY.top and offsetXY.left. Those properties of the object are not capital.
I want to visualize a huge diagram that is drawn in a HTML5 canvas. As depicted below, let’s imagine the world map, it’s impossible to visualize it all at the same time with a “decent” detail. Therefore, in my canvas I would like to be able to pan over it using the mouse to see the other countries that are not visible.
Does anyone know how to implement this sort of panning in a HTML5 canvas? Another feature would be the zoom in and out.
I've seen a few examples but I couldn't get them working nor they seam to address my question.
Thanks in advance!
To achieve a panning functionality with a peep-hole it's simply a matter of two draw operations, one full and one clipped.
To get this result you can do the following (see full code here):
Setup variables:
var ctx = canvas.getContext('2d'),
ix = 0, iy = 0, /// image position
offsetX = 0, offsetY = 0, /// current offsets
deltaX, deltaY, /// deltas from mouse down
mouseDown = false, /// in mouse drag
img = null, /// background
rect, /// rect position
rectW = 200, rectH = 150; /// size of highlight area
Set up the main functions that you use to set size according to window size (including on resize):
/// calc canvas w/h in relation to window as well as
/// setting rectangle in center with the pre-defined
/// width and height
function setSize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
rect = [canvas.width * 0.5 - rectW * 0.5,
canvas.height * 0.5 - rectH * 0.5,
rectW, rectH]
update();
}
/// window resize so recalc canvas and rect
window.onresize = setSize;
The main function in this is the draw function. Here we draw the image on the position calculated by mouse moving (see next section).
First step to get that washed-out look is to set alpha down to about 0.2 (you could also draw a transparent rectangle on top but this is more efficient).
Then draw the complete image.
Reset alpha
Draw the peep-hole using clipping with corrected offsets for the source.
-
/// main draw
function update() {
if (img === null) return;
/// limit x/y as drawImage cannot draw with negative
/// offsets for clipping
if (ix + offsetX > rect[0]) ix = rect[0] - offsetX;
if (iy + offsetY > rect[1]) iy = rect[1] - offsetY;
/// clear background to clear off garbage
ctx.clearRect(0, 0, canvas.width, canvas.height);
/// make everything transparent
ctx.globalAlpha = 0.2;
/// draw complete background
ctx.drawImage(img, ix + offsetX, iy + offsetY);
/// reset alpha as we need opacity for next draw
ctx.globalAlpha = 1;
/// draw a clipped version of the background and
/// adjust for offset and image position
ctx.drawImage(img, -ix - offsetX + rect[0], /// sx
-iy - offsetY + rect[1], /// sy
rect[2], rect[3], /// sw/h
/// destination
rect[0], rect[1], rect[2], rect[3]);
/// make a nice sharp border by offsetting it half pixel
ctx.strokeRect(rect[0] + 0.5, rect[1] + 0.5, rect[2], rect[3]);
}
Now it's a matter of handling mouse down, move and up and calculate the offsets -
In the mouse down we store current mouse positions that we'll use for calculating deltas on mouse move:
canvas.onmousedown = function(e) {
/// don't do anything until we have an image
if (img === null) return;
/// correct mouse pos
var coords = getPos(e),
x = coords[0],
y = coords[1];
/// store current position to calc deltas
deltaX = x;
deltaY = y;
/// here we go..
mouseDown = true;
}
Here we use the deltas to avoid image jumping setting the corner to mouse position. The deltas are transferred as offsets to the update function:
canvas.onmousemove = function(e) {
/// in a drag?
if (mouseDown === true) {
var coords = getPos(e),
x = coords[0],
y = coords[1];
/// offset = current - original position
offsetX = x - deltaX;
offsetY = y - deltaY;
/// redraw what we have so far
update();
}
}
And finally on mouse up we make the offsets a permanent part of the image position:
document.onmouseup = function(e) {
/// was in a drag?
if (mouseDown === true) {
/// not any more!!!
mouseDown = false;
/// make image pos. permanent
ix += offsetX;
iy += offsetY;
/// so we need to reset offsets as well
offsetX = offsetY = 0;
}
}
For zooming the canvas I believe this is already answered in this post - you should be able to merge this with the answer given here:
Zoom Canvas to Mouse Cursor
To do something like you have requested, it is just a case of having 2 canvases, each with different z-index. one canvas smaller than the other and position set to the x and y of the mouse.
Then you just display on the small canvas the correct image based on the position of the x and y on the small canvas in relation to the larger canvas.
However your question is asking for a specific solution, which unless someone has done and they are willing to just dump their code, you're going to find it hard to get a complete answer. I hope it goes well though.