Resizing rotated rectangle on HTML canvas - javascript

I have an instance of HTML 5 canvas and a rectangle drawn on it.
My drawing function takes a resizing angle into account and uses relative coordinates.
Relative coordinates're based upon three variables: top left rectangle point, rectangle width and rectangle height.
Rectangle width and rectangle height're calculated using two points: top left rectangle point and bottom right rectangle point.
To sum up, drawing function depends on top left rectangle point, bottom right rectangle point and rotation. It's an important point for the following text!
Here's a code snippet:
var canvas = document.getElementById('imageCanvas');
var ctx = canvas.getContext('2d');
var xTopLeft = 550;
var yTopLeft = 200;
var xBottomRight = 750;
var yBottomRight = 450;
var w = Math.max(xTopLeft, xBottomRight) - Math.min(xTopLeft, xBottomRight);
var h = Math.max(yTopLeft, yBottomRight) - Math.min(yTopLeft, yBottomRight);
var r = 1;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save()
ctx.translate(xTopLeft + w / 2, yTopLeft + h / 2);
ctx.rotate(r);
ctx.fillStyle = "yellow";
ctx.fillRect(w / 2 * (-1), h / 2 * (-1), w, h);
ctx.restore()
}
Here's my rectangle with a bunch of controls: eight resizing handles (white) and one rotation handle (green):
Rotating works fine.
Resizing works fine.
And I also try to implement resizing after rotation. Here's my approach with a humble illustration:
Grab the coordinates of the red point (it's mouse cursor coordiantes)
Derotate the point using negative angle to get derotated coordinates
function rotatePoint(x, y, center_x, center_y, angle) {
var new_x = (x - cx) * Math.cos(angle) - (y - cy) * Math.sin(angle) + cx;
var new_y = (x - cx) * Math.sin(angle) + (y - cy) * Math.cos(angle) + cy;
return [new_x, new_y]
}
Update xTopLeft, yTopLeft and redraw
Done
The idea behind this approach is simple. Since my drawing function depeneds on top left rectangle point and bottom right rectangle point I just need to get their new coordinates.
For instance, here's a simplified code for B point:
if (point == 'B') {
var newPointB = rotatePoint(mouse.x, mouse.y, center_x, center_y, -r);
xBottomRight = newPointB[0];
yTopLeft = newPointB[1];
}
But it doesn't work as expected: while resizing my rotated rectangle shifts, jumps and totally misbehaves.
In search of insights I've stumbled upon this article. The article covers my problem, but I don't get author's approach and can't implement it.
Why should I always lock the coordinates of the A point? My top left handle is intended to resize the rectangle in a north-west direction, so it would be necessary to change the coordinates of the A point...
Why should we recalculate the center point before derotation? It breaks the idea of uniform matrix transformations...
What's the correct algorithm in my case?

I was also facing same problem. It turned out that the angle I was using was in degree. Try multiplying angle in rotatePoint function with (Math.PI / 180).

Related

Is it possible to draw an image on a path in HTML5 canvas?

For instance, say I have the following path.
<canvas id="main" width="500" height="250"></canvas>
var canvas = document.getElementById("main");
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(20,20);
ctx.lineTo(100,20);
ctx.arcTo(150,20,150,70,50);
ctx.lineTo(150,120);
ctx.lineWidth = 3;
ctx.stroke();
Is it possible to draw images on the arc of the line? If so, How?
Slice an image to draw on curves.
Yes it is possible, though ideally this would be a job for WebGL. The next best solution is a scan line render but that is way to much CPU load for poor Javascript to manage.
The next best I mean "OK sort of." option is a little image slicing.
You simply draw the image in thin slices around the arc. The 2D renderer is not perfect and tries to draw half pixels as best it can. The result is some noise along the edge of each slice where you can see through. To overcome this I draw each slice slightly wider to cover up any holes.
If you need high quality, rendering it all at double the size on an offscreen canvas and then scale down to a onscreen canvas (don't forget smoothing) will make most think it was drawn that way.
As the inner and outer edges of the arc have different circumferences some of the image must be squashed or stretched. In the demo I keep the inner edge of the image to the correct width and stretch the outer edge. It is easy to change but ensure that you use the outer edge to workout how many slices to draw.
WARNING the radius given is for the inner edge. It is vetted to stop the for loop getting too long and blocking the page. You may want to limit the radius so the inner circumference is the same as the image width. radius = radius < img.width / (Math.PI * 2) ? img.width / (Math.PI * 2) : radius;
It is easy to adapt to lines and curves. All you need is the tangent or curve normal (should be unit vector ie length 1) Use this vector to set the transform ctx.setTransform(nx,ny,tx,ty,px,py). THe first two values point out from the bottom of the image to the top, the next two numbers are along the tangent from left to right. The last two are the point on the curve to draw the slice.
// creates a blank image with 2d context
var createImage=function(w,h){var i=document.createElement("canvas");i.width=w;i.height=h;i.ctx=i.getContext("2d");return i;}
// create a canvas and add to dom
var can = createImage(512,512);
document.body.appendChild(can);
var ctx = can.ctx;
// create a image (canvas) to draw on the arc.
const textToDisplay = "<<Image on arc>>"
ctx.font = "64px arial";
var w = ctx.measureText(textToDisplay).width + 8;
var text = createImage(w + 64,84);
text.ctx.fillStyle = "#F90";
text.ctx.strokeStyle = "black";
text.ctx.lineWidth = 16;
text.ctx.fillRect(0,0,text.width,text.height);
text.ctx.strokeRect(0,0,text.width,text.height);
text.ctx.font = "64px arial";
text.ctx.fillStyle = "#0F0";
text.ctx.strokeStyle = "Black";
text.ctx.lineWidth = 4;
text.ctx.strokeText(textToDisplay,38,58);
text.ctx.fillText(textToDisplay,38,58);
// draws image on arc
// img image to render
// x,y center of arc
// radius the inner edge (bottom of image) radius
// fromAng The angle to start drawing the image in radians
// toAng (optional if not given image width will be used to get toAng)
// returns undefined
function drawArcImage(img,x,y,radius,fromAng,toAng){
// WARNING if you let the radius get to small the ratio between the inner and out circumference
// gets very large. This will result in the image being stretched over a quintabazzilon pixels.
// so must vet the radius or you will block the page and upset the browser gods.
radius = Math.abs(radius); // only positive
radius = radius < img.height / 8 ? img.height / 8 : radius;
var outRad = radius + img.height;
var cir = Math.PI * 2 * radius; // get inner circumference
if(toAng === undefined){
var toAng = (img.width / cir) * Math.PI * 2 ; // get the angle the image will cover
}
var cirOut = toAng * outRad; // get the out edge distance in pixels
var imgStep = img.width / cirOut; // the image step per slice
var imgX = 0; // track the image line to draw
var angStep = toAng / cirOut; // the angle steps
// For each pixel on the out edge draw a slice
for(var i = 0; i < toAng; i += angStep){
var dx = Math.cos(fromAng + i);
var dy = Math.sin(fromAng + i);
// set up the transform to draw a slice from the inner to outer edges
ctx.setTransform(dy,-dx,-dx,-dy,dx * radius + x,dy * radius + y);
// get and draw the slice. I stretch it a little (2pix) to cover imperfect rendering
ctx.drawImage(img,imgX,0,imgStep,img.height,-1,-img.height,2,img.height);
// move to next slice
imgX += imgStep;
}
ctx.setTransform(1,0,0,1,0,0); // reset the transform
}
// animate the image to prove it is real.. LOL
var animTick = 0;
var animRate = 0.01;
var pos = 0;
// update function call via RAF
function update(){
animTick += animRate; // update tick
// random anim sin waves.
var rad = Math.sin(animTick) * (256-text.height - 20) + 20;
pos += Math.sin(animTick*10) * 0.02;
pos += Math.sin(animTick/ 3) * 0.02;
pos += Math.sin(animTick/ 7) * 0.05;
// clear
ctx.clearRect(0,0,can.width,can.height)
// draw
drawArcImage(text,256,256,rad,pos)
// do again and again and again
requestAnimationFrame(update);
}
update();
This is an answer to a similar question:
You could, in the draw loop implement a "line drawing algorithm" that does not exactly draw a line but draws an item at a place where that point would be. Except, replace the line algorithm here to draw an arc instead.
function line(x0, y0, x1, y1){
var dx = Math.abs(x1-x0);
var dy = Math.abs(y1-y0);
var sx = (x0 < x1) ? 1 : -1;
var sy = (y0 < y1) ? 1 : -1;
var err = dx-dy;
while(true){ // put draw loop here.
drawImage(image,x0,y0);//setPixel(x0,y0); // Do what you need to for this
if ((x0==x1) && (y0==y1)) break;
var e2 = 2*err;
if (e2 >-dy){ err -= dy; x0 += sx; }
if (e2 < dx){ err += dx; y0 += sy; }
}
}
code taken from: Bresenham algorithm in Javascript
I would suggest using a library like p5.js to do something like this. http://p5js.org

Diagonal corner to corner gradient in canvas

I need to have a diagonal gradient from corner to corner in a canvas, not css.
Check example : http://jsfiddle.net/58y8b/77/
The first box is fine, since it's a square the gradient coordinates are just top left and bottom right.
Is there a way to calculate those coordinates for the next 2 rectangles so they get the gradient from corner to corner like the first box?
Fitting a gradient to 4 corners of Rectangle
This can be done with a little trig.
The solution
The diagram shows what needs to be done.
We need to find the coords of point E and D
We have the rectangle (assume the top left is at (0,0) that has width and height W, H. We find the angle pheta (bottom left) which is the same angle as pheta on the bottom right. We need the length of the line AB which is part of a right triangle that we have the hypot H for and the angle pheta. So AB is H * cos(pheta). We then get the vector at 90 deg of the diagonal line, and set its length to AB then find the center C and subtract the vector to get E and add the vector to the center C for D
To find the angle for the diagonal line use Math.atan2(-H,W).
The code
The code for it is taken from the fiddle you supplied and for rect4
//==========================================================
// NOTE this assumes that the top left of rect is at (0,0)
// if it is not then add the top left coordinate to the
// coordinates of E and D when defining the gradient position
// all else is the same
//-----------------------------------------------------------
// get angle from bottom left to top right
var pheta = Math.atan2(-rect4.height,rect4.width);
// get the length of the line from bottom right to diagonal line
// we got the ang of.
var AB = Math.abs(rect4.height * Math.cos(pheta)); // dont need the abd but cant be bothered
// explaining why its negative
// get vector at 90 deg from found angle
var xdx = Math.cos(pheta + Math.PI/2);
var xdy = Math.sin(pheta + Math.PI/2);
// from the center C of rectangle move AB dist back and forward to find
// points E and D
var x1 = rect4.width/2 - xdx * AB;
var y1 = rect4.height/2 - xdy * AB;
var x2 = rect4.width/2 + xdx * AB;
var y2 = rect4.height/2 + xdy * AB;
// Now create the gradient from E to D
rect4.setGradient('fill', {
type: 'linear',
x1: x1,
y1: y1,
x2: x2,
y2: y2,
colorStops: gradient
});
The result
The image need no words..

HTML5 canvas translate back after scale and rotation

I'm trying to do a few thing with canvas. First I have a user upload an image, if the image is larger than I want I need to scale it down. That part is working just fine. Recently we ran into an issue with iPhone users uploading images. These have orientation issues. I've figured out how to get the orientation extracted, my issue is what happens when I manipulate the image in the canvas.
This is what I need to do: Get the image, translate(), scale(), rotate(), translate() <- get it back to its original position, drawImage().
When I do that part of the image is off in the abyss.
if (dimensions[0] > 480 || dimensions[1] > 853) {
// Scale the image.
var horizontal = width > height;
if (horizontal) {
scaledHeight = 480;
scaleRatio = scaledHeight / height;
scaledWidth = width * scaleRatio;
} else {
scaledWidth = 640;
scaleRatio = scaledWidth / width;
scaledHeight = height * scaleRatio;
}
canvas['width'] = scaledWidth;
canvas['height'] = scaledHeight;
ctx['drawImage'](image, 0, 0, width, height, 0, 0, scaledWidth, scaledHeight);
/* Rotate Image */
orientation = 8; //manual orientation -> on the site we use loadImage to get the orientation
if(orientation != 1){
switch(orientation){
case 8:
case 6:
canvas.width = scaledHeight;
canvas.height = scaledWidth;
break;
}
var halfScaledWidth = scaledWidth/2;
var halfScaledheight = scaledHeight/2;
ctx.save(); //<- SAVE
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.translate(halfScaledWidth,halfScaledheight);
switch(orientation){
case 8: //rotate left
ctx.scale(scaleRatio,scaleRatio);
ctx.rotate(-90*Math.PI/180);
ctx.translate(-1380,-1055); // <-Manuial numbers
break;
case 3: //Flip upside down
ctx.scale(scaleRatio,scaleRatio);
ctx.rotate(180*Math.PI/180);
ctx.translate(-925,-595); //<-Manuial numbers
break;
case 6: //rotate right
ctx.scale(scaleRatio,scaleRatio);
ctx.rotate(90*Math.PI/180);
ctx.translate(-462,-130); //<-Manuial numbers
break;
}
//re-translate and draw image
//ctx.translate(-halfScaledWidth,-halfScaledheight);
ctx.drawImage(image,-halfScaledWidth, -halfScaledheight);
ctx.restore(); //<- RESTORE
}
/* Rotate Image */
}
I have orientation set manually so I can see how it looks in each position Im worried about. If its a portrait orientation I flip the canvas.
I've tried save() and restore(). I've tried translate(x,y) then translate(-x,-y).. My guess is that because of the scale the grid is off and x and y need to be multiplied. I tried doing that against the scaleRatio and still didn't work.
As you can see I manually set the translate back but that only works with the image size I am working with, so not a good solution at all!
Here is the code:
JSFiddle If I do a normal rotate right it all works.
Thanks!
Transformations
For the easy answer if you are not interested in the how skip to the bottom where you will find an alternative approch to your problem. It is all commented. I have made a bit of a guess as to what you wanted.
If you are interested in what I consider a simpler way to use the 2D transformation functions read the rest.
Matrix Math
When you use translate, scale, and rotate via the canvas 2D API what you are doing is multiplying the existing matrix with one created with each function.
Basically when you do
ctx.rotate(Math.PI); // rotate 180 deg
the API creates a new rotation matrix and multiplies the existing matrix with it.
Unlike normal math multiplication matrix multiplication will change the result depending on what order you multiply. In normal math multiplication A * B = B * A but this does not hold true for matrices mA * mB != mB * mA (Note the not equals)
This becomes more problematic when you need to apply several different transformations.
ctx.scale(2,2);
ctx.rotate(Math.PI/2);
ctx.translate(100,100);
Does not give the same result as
ctx.scale(2,2);
ctx.translate(100,100);
ctx.rotate(Math.PI/2);
The order that you need to apply the tranforms depends on what you are trying to achieve. Using the API this way is very handy for complex linked animations. Unfortunately it is also a source of endless frustration if you are not aware of matrix math. It also forces many to use the save and restore functions to restore the default transformation, that in some situations can be very costly in GPU performance.
setTransform()
We are in luck though as the 2D API also has the function ctx.setTransform(a, b, c, d, e, f) which is all you really should ever need. This function replaces the existing transform with the one supplied. Most of the documentation is rather vague as to the meaning of the a,b,c,d,e,f but contained in them is the rotation, scale, and translation.
One handy use of the function is to set the default transform rather than use save and restore.
I see this type of thing a lot. (The Example 1, referenced further down)
// transform for image 1
ctx.save(); // save state
ctx.scale(2,2);
ctx.rotate(Math.PI/2);
ctx.translate(100,100);
// draw the image
ctx.drawImage(img1, -img1.width / 2, -img1.height / 2);
ctx.restore(); // restore saved state
// transform for image 2
ctx.save(); // save state agian
ctx.scale(1,1);
ctx.rotate(Math.PI);
ctx.translate(100,100);
// draw the image
ctx.drawImage(img2, -img2.width / 2, -img2.height / 2);
ctx.restore(); // restore saved state
An easier way is to just drop the save and restores and reset the transform manually by setting it to the Identity matrix
ctx.scale(2,2);
ctx.rotate(Math.PI/2);
ctx.translate(100,100);
// draw the image
ctx.drawImage(img1, -img1.width / 2, -img1.height / 2);
ctx.setTransform(1,0,0,1,0,0); // restore default transform
// transform for image 2
ctx.scale(1,1);
ctx.rotate(Math.PI);
ctx.translate(100,100);
// draw the image
ctx.drawImage(img2, -img2.width / 2, -img2.height / 2);
ctx.setTransform(1,0,0,1,0,0); // restore default transform
Now then I am sure you are still wondering what are these numbers being passed to setTransform and what do they mean?
The easiest way to remember them is as 2 vectors and 1 coordinate. The two vectors describe the direction and scale of a single pixel, the coordinate is simply the x,y pixel location of the origin (the location that drawing at 0,0 will be on the canvas).
A Pixel and its axis
Imagine a single pixel, this is the abstract transformed pixel that can be scaled and rotated by the current transformation. It has two axis, X and Y. To describe each axis we need two numbers ( a vector) these describes the screen (untransformed) direction and scale of the top and left side of the pixel. So for a normal pixel that matches the screen pixels the X axis is across the top from left to right and is one pixel long. The vector is (1,0) one pixel across, no pixels down. For the Y axis that goes down the screen the vector is (0,1) no pixels across, one pixel down. The origin is the top right screen pixel which is at coordinate (0,0).
Thus we get the Identity Matrix, the default matrix for the 2D API (and many other APIs) The X axis (1,0), Y axis (0,1) and the origin (0,0) which match the six arguments for setTransform(1,0,0,1,0,0).
Now say we want to scale the pixel up. All we do is increase the size of the X and Y Axis setTransform(2,0,0,2,0,0) is the same as scale(2,2) (from the default transform) Our pixel's top is now two pixels long across the top and two pixels long down the left side. To scale down setTransform(0.5,0,0,0.5,0,0) our pixel is now half a pixel across and down.
These two axis vectors (a,b) & (c,d) can point in any direction, are completely independent of each other , they don't have to be at 90 deg to each other so can skew the pixel, nor do they require that they be the same length so you can change the pixel aspect. The origin is also independent and is just the canvas absolute coordinates in pixels of the origin and can be set to anywhere on or off the canvas.
Now say we want to rotate the transform 90Deg clockwise, scale up both axes by 2 and position the origin at the center of the canvas. We want the X axis (top) of our pixel to be 2 pixels long and pointing down the screen. The vector is (0,2) 0 across and two down. We want the left side of our pixel to 2 long and point to the left of the screen (-2,0) Negative two across and none down. And the origin at the center is (canvas.width / 2, canvas.height / 2) to get the final matrix that is setTransform(0,2,-2,0,canvas.width / 2, canvas.height / 2)
Rotate the other way is setTransform(0,-2,2,0,canvas.width / 2, canvas.height / 2)
Easy Rotate 90deg
You may notice that rotating 90 degrees is just swapping the vectors and changing a sign.
The vector (x,y) rotated 90 degrees clockwise is (-y,x).
The vector (x,y) rotated 90 degrees anti-clockwise is (y,-x).
Swap the x, and y and negate the y for clockwise or negate the x for the anticlockwise rotation.
For 180 it is starting at 0 deg vector (1,0)
// input vector
var x = 1;
var y = 0;
// rotated vector
var rx90 = -y; // swap y to x and make it negative
var ry90 = x; // x to y as is
// rotate again same thing
var rx180 = -ry90;
var rx180 = rx90;
// Now for 270
var rx270 = -ry180; // swap y to x and make it negative
var rx270 = rx180;
Or all in terms of just x and y
0 deg (x,y)
90deg (-y,x)
180deg (-x,-y)
270deg (y,-x)
and back to 360 (x,y).
This is a very handy attribute of a vector that we can exploit to simplify the creation of our transformation matrix. In most situations we do not want to skew our image thus we know that the Y axis is always 90Deg clockwise from the x axis. Now we only need to describe the x axis and by applying the 90deg rotation to that vector we have the y axis.
So the vars x and y are the scale and direction of the top of our pixel (x axis), ox, oy are the location of the origin on the canvas (translation) .
var x = 1; // one pixel across
var y = 0; // none down
var ox = canvas.width / 2; // center of canvas
var oy = canvas.height / 2;
Now to create the transform is
ctx.setTransform(x, y, -y, x, ox, oy);
Note that the y axis is at 90 degs to the x axis.
Trig and the Unit vector
All well and easy when the axis are aligned to the top and sides, how do you get the vector for a axis at an arbitrary angle such as is supplied by the argument for ctx.rotate(angle) For that we need a tiny bit of trig. The Math function Math.cos(angle) returns the x component of the angle, angle and Math.sin(angle) gives us the Y component. For zero deg cos(0) = 1 and sin(0) = 0 for 90 deg (Math.PI/2 radians) cos(PI/2) = 0 and sin(PI/2) = 1.
The beauty of using sin and cos is that the two numbers that we get for our vector always give us a vector that is 1 unit (pixel) long (this is called a normalised vector or a unit vector) thus cos(a)2 + sin(a)2 = 1
Why does this matter? because it makes scaling very easy. Assuming that we always keep the aspect square we only need one number for the scale. To scale a vector you simply multiply it by the scale
var scale = 2; // scale of 2
var ang = Math.random() * 100; // any random angle
var x = Math.cos(ang); // get the x axis as a unit vector.
var y = Math.sin(ang);
// scale the axis
x *= scale;
y *= scale;
the vector x,y is now two units long.
Better than using save, restore, rotate, scale, translate... :(
Now put it all together to create a matrix with an arbitrary rotation, scale and translation (origin)
// ctx is the 2D context,
// originX, and originY is the origin, same as ctx.translate(originX,originY)
// rotation is the angle in radians same as ctx.rotate(rotation)
// scale is the scale of x and y axis same as ctx.scale(scale,scale)
function createTransform(ctx,originX,originY,rotation,scale){
var x, y;
x = Math.cos(rotation) * scale;
y = Math.sin(rotation) * scale;
ctx.setTransform(x, y, -y, x, originX, originY);
}
Now to apply that to the example (1) given above
// dont need ctx.save(); // save state
// dont need ctx.scale(2,2);
// dont need ctx.rotate(Math.PI/2);
// dont need ctx.translate(100,100);
createMatrix(ctx, 100, 100, Math.PI/2, 2)
// draw the image normally
ctx.drawImage(img1, -img1.width / 2, -img1.height / 2);
// dont need ctx.restore(); // restore saved state
// transform for image 2
// dont need ctx.save(); // save state agian
// dont need ctx.scale(1,1);
// dont need ctx.rotate(Math.PI);
// dont need ctx.translate(100,100);
// we don't have to reset the default transform because
// ctx.setTransform completely replaces the current transform
createMatrix(ctx, 100, 100, Math.PI/2, 2)
// draw the image
ctx.drawImage(img2, -img2.width / 2, -img2.height / 2);
// dont need ctx.restore(); // restore saved state
And that is how you use setTransform to simplify transforming the canvas, rather than guessing, trial and error, scale, rotates, and translates back and forth within a sea of save and restores.
Using that to simplify your code
The answer
And now to your question
I am not entirely sure what you are after, I presume you a dont mind scaling the canvas to accommodate the image, that the image is always in the center and that the aspect remains the same. As the rotations are aligned to the screen I will set the transforms manualy
// this is in set up code
const MAX_SIZE_WIDTH = 640;
const MAX_SIZE_HEIGHT = 480;
orientationData = [];
orientationData[6] = Math.PI/2; // xAxis pointing down
orientationData[8] = -Math.PI/2; // xAxis pointing up
orientationData[3] = Math.PI; //xAxis pointing to left
// in your code
var orient,w,h,iw,ih,scale,ax,ay; // w and h are canvas size
// assume image is the loaded image
iw = image.width; // get the image width and height so I dont have to type as much.
ih = image.height;
if(orientation != 1){
var orient = orientationData[orientation];
if(orient === undefined){
return; // bad data so return
}
// get scale and resize canvas to suit
// is the image on the side
if(orientation === 6 || orientation === 8){
// on side so swap width and height
// get the height and width scales for the image, dont scale
// if the image is smaller than the dimension
scale = Math.min(1,
MAX_SIZE_WIDTH / ih,
MAX_SIZE_HEIGHT / iw
);
w = canvas.width = scale * ih;
h = canvas.height = scale * iw;
}else{
// for normal orientation
scale = Math.min(1,
MAX_SIZE_WIDTH / iw,
MAX_SIZE_HEIGHT / ih
);
h = canvas.height = scale * ih;
w = canvas.width = scale * iw;
}
// Do you really need to clear the canvas if the image is filling it??
// ensure that the default transform is set
ctx.setTransform(1, 0, 0, 1, 0, 0);
// clear the canvas
ctx.clearRect(0, 0, w, h);
// now create the transformation matrix to
// position the image in the center of the screen
// first get the xAxis and scale it
ax = Math.cos(orient) * scale;
ay = Math.sin(orient) * scale;
// now set the transform, the origin is always the canvas center
// and the Y axis is 90 deg clockwise from the xAxis and same scale
ctx.setTransform(ax, ay, -ay, ax, w / 2, h / 2);
// now draw the image offset by half its width and height
// so that it is centered on the canvas
ctx.drawImage(image,-iw / 2, -ih / 2);
// restore the default transform
ctx.setTransform(1, 0, 0, 1, 0, 0);
} // done.
Try this one https://jsfiddle.net/uLdf4paL/2/. Your code is correct, but you have to change orientation variable when you try to rotate and scale image (if it is what you wanna get).
My solution for this was using two canvases
one for the rotation and scaling (around the center point of the image)
then using a new canvas for translating and drawing the prev canvas on it. it worked perfectly for me:
const root = document.querySelector("#root");
const img = document.querySelector('#img');
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext("2d");
const tempCanv = document.createElement('canvas')
const tempCtx = tempCanv.getContext('2d');
canvas.height = img.height
canvas.width = img.width
tempCanv.width = img.width
tempCanv.height = img.height
const position = [0.25, 0.25];
const rotation = -40;
const scale = [37.222, 37.222];
const x = img.width / 2;
const y = img.height / 2;
// Set the origin to the center
tempCtx.translate(x, y);
// now rotate and scale around the center
tempCtx.rotate(Math.PI / 180 * rotation);
tempCtx.scale(scale[0] / 100, scale[1] / 100);
// Translate back to origin
tempCtx.translate(-x, -y);
// Draw temp
tempCtx.drawImage(img,0,0);
// Translate the final canvas
ctx.translate(position[0] * canvas.width, position[1] * canvas.height);
ctx.drawImage(tempCanv, 0, 0);
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#root{
display: flex;
gap: 2rem;
padding: 1rem;
overflow:visible
position relative;
}
.image-container{
}
#img{
transform: rotate(-40deg) scale(0.37222, 0.37222);
top: 25%;
left: 25%;
position: relative;
}
</style>
</head>
<body>
<div id="root">
<div class="image-container">
<img id="img" src="https://media.istockphoto.com/photos/cat-world-picture-id1311993425?b=1&k=20&m=1311993425&s=170667a&w=0&h=vFvrS09vrSeKH_u2XZVmjuKeFiIEjTkwr9KQdyOfqvg=" alt="red">
</div>
<canvas id="canvas"></canvas>
</div>
</body>
</html>

HTML5 Canvas : Colliding a full circle with a segmented circle

Edit: I could divide the radius with the angle?
Problem: For the sake of learning the arts of collision in HTML5 Canvas, I am currently trying to get a full circle to collide with a segmented circle, in this case a semi circle.
What I Tried: My first thought was a simple circle to circle collision would do but I was wrong. I read various sources on collision detection but all of them were either the standard circle / circle, box / circle, box / box, or polygon collision formulas.
My Question: What is the formula for colliding a full circle with only a segmented circle? It seems something other than just the radius comes into play. Maybe the radians as well?
Attempt:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var C1 = {x: 45, y: 65, radius: 20};
var C2 = {x: 60, y: 20, radius: 20};
var dx = C1.x - C2.x;
var dy = C1.y - C2.y;
var distance = Math.sqrt(dx * dx + dy * dy);
ctx.beginPath();
ctx.arc(C1.x, C1.y, C1.radius, 0, Math.PI * 2);
ctx.fillStyle = 'green';
ctx.fill();
ctx.beginPath();
ctx.rotate(0.3);
ctx.arc(C2.x, C2.y, C2.radius, 0, Math.PI * 1);
ctx.fillStyle = 'red';
ctx.fill();
if (distance < C1.radius + C2.radius) {
alert('true')
}
else {
alert('false')
}
A demo for to play around with: jsfiddle.net/tonyh90g/1
My learning resource: https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection
You're on the right tracks, you will indeed need to not only calculate the distance between centres but also the angle of the intersection point(s) on the segment.
In general cartesian coordinates that angle can be calculated using Math.atan2(dy, dx) where dx and dy are the difference in coordinates from the segment to the circle. If those angles fall between the segment's angle, you have a hit.
[NB: At the exact point of touching there's only one point, but if the two objects end up slightly overlapping, which is not uncommon in animation, you'll get two points].
It's also possible that the circle could intersect the line segments rather than the round portion of the segment, but I think those cases will be correctly caught anyway. This may require further investigation, and if required would need a line-segment / circle intersection test.
I also note in your fiddle that you're using rotate() transforms on the sector. This will foul up your angle calculations unless you specifically account for it. It may be easier to use absolute angles throughout.

Canvas - How to make line pass through a point

This is what I have right now:
I want to make it like this:
The blue line as you can see passes through the center(400,400). The start of the blue line is not fixed, it moves according to data that the user enter.
How do I add this blue line and make it pass through the center?
Sorry for my bad English, working on that :)
Halfon
Use mathematics.
Let the center co-ordinates be (Cx, Cy), which in your case are (400, 400). Let the user's co-ordinates be (Ux, Uy).
The equation of the line passing through the center from (Ux, Uy) would be given by the equation:
y - Cy = slope * (x - Cx), where slope = (Cy - Uy) / (Cx - Ux).
Now, to draw the line from Ux to some x co-ordinate, say Px, simply use the equation to calculate Py = Slope * (Px - Cx) + Cy, then draw the line from (Ux, Uy) to (Px, Py).
context.beginPath();
context.moveTo(Ux, Uy);
context.lineTo(Px, Py);
context.stroke();
Simply draw the line through the center point and extend its length. It's completely unclear what the length of the line is because you failed to provide any code, so I'm just doubling it in the example.
To calculate the end points, just subtract the starting x/y coordinates from the doubled x/y coordinates of the central point.
I wrote you a dynamic example which takes the mouse coordinates as a starting position, but the same principle applies if you only have a single static point from the user input. Try it here:
var canvas = document.getElementById('c');
var context = canvas.getContext('2d');
var centerPoint;
function setSize() {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
centerPoint = {x: canvas.width / 2, y: canvas.height / 2};
}
canvas.addEventListener('mousemove', function(e) {
canvas.width = canvas.width;
context.beginPath();
context.moveTo(e.offsetX, e.offsetY);
context.lineTo(centerPoint.x * 2 - e.offsetX, centerPoint.y * 2 - e.offsetY);
context.lineWidth = 3;
context.strokeStyle = '#0000ff';
context.stroke();
})
canvas.addEventListener('mousedown', function(e) {
centerPoint = {x: e.offsetX, y: e.offsetY};
canvas.width = canvas.width;
})
window.addEventListener('resize', setSize);
setSize()
canvas {width: 100%;height: 100%;position: absolute;}
body {margin: 0}
p {position: absolute; pointer-events: none}
<canvas id="c"></canvas>
<p>Click anywhere to set the center point (dead center by default)</p>

Categories