Canvas context.drawimage doesn't draw on the right coordinates - javascript

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.

Related

Javascript Canvas issue: Why do my points on the canvas not correspond properly to the height of the graph?

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

How to use html2canvas with coordinates?

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.

Rotating Image in Canvas, causing stretch of Image (getting wider and less high)

I am running into a weird problem, while performing a simple rotation of an image.
While rotating an image around its center in a canvas, the image is stretched to double of its original width when rotating to 90° and is resizing to its normal width/height rotating to 180° then its stretching again to 270° and resizing back when rotating to 0° again.
Also it seems like the Image is between those 90° steps a parallelogram and not a rectangle.
What I actually do is:
var TO_RADIANS = Math.PI/180;
function drawRotatedImage(x, y, angle, width, height) {
contextL.save();
contextL.translate(x + width/2, y + height/2);
contextL.rotate(angle * TO_RADIANS);
var imgT = new Image();
imgT.src = myPath;
contextL.drawImage(imgT, -width/2, -height/2, width, height);
contextL.restore();
}
ContextL is created like this:
canvasL = document.getElementById("canvasL");
contextL = canvasL.getContext("2d");
I really do not get, what is my fault here. I have read nearly all threads about rotating in canvas and all are doing exactly the same, but it is working. Only special may be, that I use drawImage(image, x, y, width, height) and not drawImage(image, x, y) but is this causing the trouble?
*With drawImage(image, x, y) same trouble is caused
*using contextL.drawImage(imgT, -width/2, -height/2, width/2, height*2); turns the effect around. Starting stretched and regular size at 90°, so it really it is getting the double of its width and half of its height when rotating to 90°.
*Width and Height are calculated as follows:
this.prepareIMG = function(){
this.widthIMG = this.img.width * this.scale;
this.heightIMG = this.img.height * this.scale;
};
This is the function I call every time right before I call drawRotatedImage(...). this.scaleis a value from 0.1to x increasing/decreasing in steps of 0.1.
Also when I log the width and height during the rotation to the console, the value stays always the same!
I do not manipulate the contextLsomewhere outside the function drawRotatedImage(...)!
This is the code which calls the rotate-function
this.rotatePlus = function(){
refreshCanvas();
this.rotation += 5;
this.prepareIMG();
drawRotatedImage(contextL, this.transX, this.transY, this.rotation, this.widthIMG, this.heightIMG/2);
};
Finally refreshCanvas() is defined as follows
function refreshCanvas(){
contextL = canvasL.getContext("2d");
contextL.clearRect(0, 0, canvasL.width, canvasL.height);}
and my canvas is declared in HTML like this <div id="divCanvasL"><canvas id="canvasL"></canvas></div> width & height are set by css.

Scale image in Canvas with offset origin

I've been struggling with this for a few days now. My question is based on code you can find here - http://codepen.io/theOneWhoKnocks/pen/VLExPX. In the example you'll see 3 images, the first scales from the [0,0] origin, the second from the center of the canvas, and the third I want to scale from the center of the offset image.
Basically I want the image to scale up or down, but stay centered on the characters iris. Below you'll find a snippet of code that controls the rendering of the third image.
function renderOffset(){
var dims = getScaledDims();
paintBG(ctx3);
ctx3.drawImage(loadedImg, offsetX, offsetY, dims.width, dims.height);
drawCenterAxis(ctx3);
}
After much Googling and looking through forums I figure I need to utilize the transformMatrix, but nothing I've tried thus far has worked. I look forward to any ideas or suggestions you may have, and thank you for your time.
Further clarification
I'm creating an image editor. For the particular use case I'm presenting here, a user has moved the image to the left 108px & up 8px.
var offsetX = -108;
var offsetY = 8;
When the user scales the offset image, I want it to scale at the center of the viewable canvas area (the red crosshairs, or in this case the characters iris).
Update
I've updated the codepen link to point to the final code. Below is a list of additions:
Added in some of the code mentioned in the accepted answer.
Added the ability to drag the image around.
Added a visual tracker for the offset.
The trick is understanding the way that scale changes a number of variables. Firstly, it changes how much of the source image is visible on the canvas. Next, this in combination with the desired center-point of the scaling influences where in the image we should start drawing from.
With a scale of 1.0, the number of pixels of the source image shown is equal to the number of pixels that the dst canvas has. I.e, if the canvas is 150x150, we can see 150x150 of the input pixels. If however, the scale is 2.0, then we wish to draw things 2 times the size. This then means that we only wish to display 75x75 pixels of the src image on the 150x150 pixels of the dst canvas. Likewise, if we wish to draw at a scale of 0.5, we should expect to see 300x300 pixels of the src image displayed in the 150x150 of the dst canvas. Perhaps you can see the relationship between scale and canvas size by now.
With this in mind, we can set about determining how much of the src image we wish to see. This is straight-forward:
var srcWidth = canvas.width / scale;
var srcHeight = canvas.height / scale;
Now that we know how much of the image will be shown, we can set about determining where in the image we should start drawing from. Since we have a specified center-point for the scaling, we know that this point should always remain in the center of the canvas.
If we remove scaling from the equation, and use the figures from earlier we can see that we want to display 150x150 pixels of the src image, and that we will need to start drawing 75pixels above and to the left of our center-point. Doing so will draw 150x150 pixels of the source image and place our center-point right smack in the middle of the canvas.
If we then re-consider scaling, we know that we're not always going to be drawing 150x150 pixels of the src image, which means that we can't just blindly start 75pixels left and above our center-point - we will have to scale this 75pixels. Since this 75 pixels is equal to half of the width and half of the height of the portion of the image we'll be displaying, we can work out the point at which to start drawing the image by dividing the srcWidth and srcHeight by 2 and then subtracting this value from the center-point.
Doing so gives us the following expression:
ctx.drawImage(image, imgCenterX-(srcWidth/2), imgCenterY-(srcHeight/2), srcWidth, srcHeight, 0,0, canvas.width, canvas.height);
When I put both of these together into a functioning sample, I ended-up with this:
"use strict";
var imgOriginX = 182, imgOriginY = 66;
function byId(id,parent){return (parent == undefined ? document : parent).getElementById(id);}
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded()
{
var targetCanvas = byId('canvas3');
var srcImage = byId('img1');
drawImageScaled(targetCanvas, srcImage, imgOriginX, imgOriginY)
drawCrosshair( byId('canvas3') );
byId('scaleSlider').addEventListener('input', onScaleSliderChange, false);
}
/*
code for scaling an image about an arbitrary point
*/
// canvas - target canvas element
// image - target canvas element
// imgCenterX - x coord of point of scaling centre-point (unit: pixels)
// imgCenterY - y coord of point of scaling centre-point (unit: pixels)
// scale - 1.0 = 100%
function drawImageScaled(canvas, image, imgCenterX, imgCenterY, scale)
{
if (scale === undefined)
scale = 1.0;
var ctx = canvas.getContext('2d');
ctx.clearRect(0,0,canvas.width,canvas.height);
var srcWidth = canvas.width / scale;
var srcHeight = canvas.height / scale;
ctx.drawImage(image, imgCenterX-(srcWidth/2), imgCenterY-(srcHeight/2), srcWidth, srcHeight, 0,0, canvas.width, canvas.height);
}
function drawCrosshair(canvas)
{
var ctx = canvas.getContext('2d');
var width, height;
width = canvas.width;
height = canvas.height;
ctx.save();
ctx.beginPath();
ctx.moveTo(width/2, 0);
ctx.lineTo(width/2, height);
ctx.moveTo(0, height/2);
ctx.lineTo(width, height/2);
ctx.closePath();
ctx.strokeStyle = "red";
ctx.stroke();
ctx.restore();
}
function onScaleSliderChange(evt)
{
var curValue = this.value;
var scale = curValue / 100;
var tgt, src;
tgt = byId('canvas3');
src = byId('img1');
drawImageScaled(tgt, src, imgOriginX, imgOriginY, scale);
drawCrosshair(tgt);
}
input[type=range]
{
width: 18px;
height: 122px;
-webkit-appearance: slider-vertical;
}
canvas
{
border: solid 1px #888;
}
img{ display:none;}
<img id='img1' src='https://i.stack.imgur.com/aFbEw.png'/>
<hr>
<canvas id='canvas3' width=150 height=150>Canvas not supported. :(</canvas>
<input id='scaleSlider' type="range" class="scale-slider js-scaleSlider" min="0" max="200" value="100" orient="vertical"/>
Here's how pull a specified [eyeX,eyeY] to center canvas and zoom the image:
Pull the eye to canvas [0,0] by multiplying -eyeX & -eyeY by the scaling factor.
Push the eye to center canvas by adding half the canvas width,height.
Scale the image by the scaling factor.
Use context.drawImage to draw the image on the canvas.
Example:
context.drawImage(
// start with the image
img,
// scale the eyeX offset by the scaling factor
// and then push the image horizontally to center canvas
-eyeX*scale + canvas.width/2,
// scale the eyeY offset by the scaling factor
// and then push the image vertically to center canvas
-eyeY*scale + canvas.height/2,
// scale whole image by the scaling factor
canvas.width*scale,
canvas.height*scale
);
Illustrations: Centered Eye at 100% and 175%
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
var eyeX=182;
var eyeY=66;
var scale=1.00;
$myslider=$('#myslider');
$myslider.attr({min:25,max:250}).val(100);
$myslider.on('input change',function(){
scale=parseInt($(this).val())/100;
drawAll(eyeX,eyeY,scale);
});
var iw,ih;
var img=new Image();
img.onload=start;
img.src="https://i.stack.imgur.com/aFbEw.png";
function start(){
iw=cw=canvas.width=img.width;
ih=ch=canvas.height=img.height;
drawAll(eyeX,eyeY,scale);
}
function drawAll(x,y,scale){
ctx.clearRect(0,0,cw,ch);
centerAndZoom(x,y,scale);
drawCrosshairs();
}
function centerAndZoom(x,y,scale){
ctx.drawImage(
img,
-x*scale+iw/2,
-y*scale+ih/2,
iw*scale,
ih*scale
);
}
function drawCrosshairs(){
ctx.beginPath();
ctx.moveTo(cw/2,0);
ctx.lineTo(cw/2,ch);
ctx.moveTo(0,ch/2);
ctx.lineTo(cw,ch/2);
ctx.stroke();
}
body{ background-color: white; }
#canvas{border:1px solid red; margin:0 auto; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
Scale: <input id=myslider type=range><br>
<canvas id="canvas" width=300 height=300></canvas>

Drawing on Canvas

I am drawing a cirlce on canvas who's center points are the middle points of the canvas so the circle should start from the center of the page and its radius should be from 1/2 of the screen to 3/4th. I have figured out the way to make the canvas resize itself according to the window size, but i cant get the cirlce to resize automatically. also that i cant figure out the radius to make it look like a cirlce with my specifications, currently it looks like an stretched circle. What value should i give to my radius to make it look like a normal circle again. also it should be able to resize it self on window resize?
currently i have the following values:
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
var myRadius = canvas.width / 4;
context.arc(centerX, centerY, myRadius, 0, (Math.PI/180) * 360 , false);
context.fillStyle = 'green';
context.fill()
}
You should handle resize events and redraw the circle for the new canvas dimensions.
If you will resize your canvas using styles circle will redraws as you expect: fiddle (actually whole canvas will act as just an image: try to change dimensions of canvas from 1000 to 100 and you'll see what I mean)
css:
#canvas {
height: 100%;
width: 100%
}
html:
<canvas id="canvas" height="1000" width="1000"></canvas>

Categories

Resources