Mouse on center of moving image on canvas - javascript

I am making for school a star catching game.
I want the enviroment to change dynamicly so that when i resize browser windows the game will resize with it.
I got the following running code:
http://jsfiddle.net/xigolle/yA74f/
The only problem with that is that the mouse isn't center on the witch.
What is the best way for me to get the mouse on the center on every size?
The problem lays for sure in this part:
ctx.drawImage(img, this.x, this.y, canvas.width/10, canvas.height/10);
The size of the browser window i get from a event listener who activates when i resize.
And the value is put in canvas.width and canvas.height.
I hope you guys can help me :)
For any more question or unclearance please ask :)

You have two problems
The first is your use of drawImage
ctx.drawImage(img, this.x, this.y, canvas.width/10, canvas.height/10);
This is going to rescale the witch image in a way that does not keep the proportions, which is why when resizing the window the witch either squishes or expands
You should resize the image based on a ratio of the original image size and original canvas size. Then use that ratio times the new canvas size to get the right image size.
//Original canvas width/height
var initialWidth = 500, initialHeight = 500;
var initialImgWidth = 120, initialImgHeight = 65;
var wRatio = initialImgWidth/initialWidth, hRatio = initialImgHeight/initialHeight;
...
ctx.drawImage(img, this.x, this.y, canvas.width*wRatio, canvas.height*hRatio);
Now that we have the image resize resolved now we can center the image on the mouse
Now to center you have to take the mouse x/y and minus each with 1/2 of width/height of the rescaled witch respectively
Witch.x = event.pageX-((canvas.width*wRatio)/2);
Witch.y = event.pageY-((canvas.height*hRatio)/2);
JSFiddle
EDIT
My rescale calculations were wrong, for now to scale the image for now just scale it by its original dimensions
var imgWScale = initialImgWidth/2;
var imgHScale = initialImgHeight/2;
...
ctx.drawImage(img, this.x, this.y, imgWScale,imgHScale);
...
Witch.x = event.pageX-(imgWScale/2);
Witch.y = event.pageY-(imgHScale/2);
Just remember to center just get the images width/height and divide in half and then take that from the mouse coordinates.

Related

Responsive canvas keeping given aspect ratio

I'm developing a drawing app, and I've encountered a problem which I have been trying to solve but I haven't found a reliable solution yet:
Sketches have an original resolution of 1280 x 720, and I would like to resize the canvas when the page is opened to the maximum size possible (given the screen dimensions minus a toolbar I have put on the upper side of the page) keeping the 1280 x 720 aspect ratio.
The canvas needs to be centered in the screen and black stripes will cover the rest: when the the browser window dimensions relation is less than the sketch original w/h relation there will be horizontal black stripes (fig 1), and when it is bigger, vertical black stripes (fig 2).
I've tried using javascript applying to the canvas the offsetWidth and offsetHeight of a div containing an auxiliar invisible image which I generate programatically with the original sketch dimensions, but doesn't seem to be a robust way to do this. Most of the time the offset properties aren't ready when the page is loaded and I have to wait for them using a timer (or mutation observers).
Some images of what I'm trying to do:
I've spend much time on this little thing and is driving me crazy, because every solution I found is way too hacky or unreliable. Any help will be very appreciated.
I'm using Angular + Ionic 4.
If I'm understanding you correctly, you want to dynamically change the size of your canvas based on how big the window is. The one stipulation is that it must maintain the 1280 x 720 (1:0.5625) ratio.
I'm going to attempt to provide a solution that will also listen for resizing of the window after it's been loaded. Keep in mind that this means you'll need to redraw everything on the canvas from there.
HTML
<body>
<canvas id="canvas"></canvas>
</body>
CSS
body {
background-color: black;
}
JavaScript:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
startListening();
function startListening() {
// resize the canvas every time the window is adjusted
window.addEventListener('resize', resizeCanvas, false);
// manually call the resizeCanvas() function as the window loads with it's starting dimensions (before a resize has happened)
resizeCanvas();
}
function resizeCanvas() {
// if the canvas height isn't maxed out, match it to the width of the page at the correct aspect ration (1:0.5625)
if(canvas.height < window.innerHeight){
canvas.width = window.innerWidth;
canvas.height = canvas.width * 0.5625;
}
// if the canvas height is maxed out, stop widening the canvas and lock the canvas height at 0.5625 of that locked height
if(canvas.height >= window.innerHeight){
canvas.height = canvas.width * 0.5625;
}
// lastly, if we are shrinking the width back down, readjust both the width and height to match
if(canvas.width > window.innerWidth){
canvas.width = window.innerWidth;
canvas.height = canvas.width * 0.5625;
}
// redraw the canvas with our newly calculated dimensions
redraw();
}
function redraw() {
ctx.fillStyle = "#FF0000";
// Draw the canvas in the center of the screen at the height and width calculated above in the resizeCanvas() function
ctx.fillRect((window.innerWidth - canvas.width) / 2, (window.innerHeight - canvas.height) / 2, canvas.width, canvas.height);
}

Canvas - How to rotate vertical/horizontal image correctly?

I've looked at many of the rotate canvas/scaling canvas resolved issues, but the solutions didn't really solve this issue, so still unsure how to get it to work correctly with canvas.
There is a vertical fixed dimensions rectangle 100w × 150h, shown as the red border below. When an image (vertical/horizontal/square) is added, and rotated, it should rotate and be scaled correctly within the vertical fixed dimensions rectangle, as shown in the example below.
In the first example, we'll go with a vertical image (Eiffel tower original image at 240w × 400h), this is what it should look like at all four rotation angles:
In the second example, we'll go with a horizontal image (Dog original image at 1280w × 720h), this is what it should look like at all four rotation angles:
What would be the most efficient way to accomplish this using canvas?
(I know css can be used transform: rotate(90deg)and play around with the background size/position properties, but I'm trying to learn how to accomplish the example above using canvas for vertical/horizontal/square images).
Here is a fiddle.
We don't need any of the canvas.width/2-image.width/2 code, so change your onload to simply by using ctx.drawImage(image,0,0, canvas.width, canvas.height). Along with this you can define a global ratio variable that will be used for scaling correctly when you rotate sideways and need to scale upwards:
var ratio = 1;
image.onload=function(){
canvas.width = 100;
canvas.height = 150;
ratio = canvas.height/canvas.width; // We will use this for scaling the image to fit
ctx.drawImage(image,0,0, canvas.width, canvas.height);
}
Now the best way to rotate a image by it's center is to translate the image center to the (0,0) point of the canvas. Then you can rotate and move it back to where it was. This is because when a rotation is applied the canvas (0,0) point is the point of rotation.
function drawRotated(degrees){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.save();
ctx.translate(canvas.width/2,canvas.height/2); // Move image center to 0,0
ctx.rotate(degrees*Math.PI/180); // Rotate will go from center
ctx.translate(-canvas.width/2,-canvas.height/2); // Move image back to normal spot
ctx.drawImage(image,0,0, canvas.width, canvas.height)
ctx.restore();
}
With the code so far the normal and 180 degree images look fine. But the sideways ones need to be scaled upwards, to do that add in some logic to detect if the image is flipped to the left or right and then scale by the ratio variable (1.5 in this case).
function drawRotated(degrees){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.save();
ctx.translate(canvas.width/2,canvas.height/2);
ctx.rotate(degrees*Math.PI/180);
if((degrees - 90) % 180 == 0) // Is the image sideways?
ctx.scale(ratio, ratio); // Scale it up to fill the canvas
ctx.translate(-canvas.width/2,-canvas.height/2);
ctx.drawImage(image,0,0, canvas.width, canvas.height)
ctx.restore();
}
Updated Fiddle
Update:
The reason that horizontal images look odd is due to two things. Currently the scaling assumes the image needs to be zoomed in when it's sideways, in the event of horizontal images that logic is flipped. Instead we want to zoom in when we are flipped normally or upside-down:
function drawRotated(degrees) {
ctx.clearRect(0,0,canvas.width,canvas.height);
...
if(imgRatio < 1) angleToScale += 90
if(angleToScale % 180 == 0)
ctx.scale(ratio, ratio);
ctx.translate(-canvas.width/2,-canvas.height/2);
...
}
Here we are determining based on if imgRatio < 1 we will claim the image is horizontal. Otherwise it will be vertical. While this is a bit broad of a stroke on claiming vertical vs horizontal, it will work for the purposes assuming we just have vertical or horizontal images.
Although even after these changes something is still off (see this fiddle). This is because when we draw the image we are fitting it to the canvas which is vertical, causing the image to stretch when it's drawn to the canvas.
This can be fixed by changing the location of where we draw the image destination. For horizontal images we want to draw it horizontally:
One note is some changes to the onload method:
var ratio = 0;
var xImgOffset = 0;
var yImgOffset = 0;
image.onload=function(){
canvas.width = 100;
canvas.height = 150;
ratio = canvas.height/canvas.width;
var imgRatio = image.height/image.width;
if(imgRatio < 1) { // Horizonal images set Height then proportionally scale width
var dimDiff = image.height/canvas.width;
image.height = canvas.width; // This keeps in mind that the image
image.width = image.width / dimDiff; // is rotated, which is why width is used
} else { // Verticle images set Height then proportionally scale height
var dimDiff = image.width/canvas.width;
image.width = canvas.width;
image.height = image.height / dimDiff;
}
xImgOffset = -(image.width - canvas.width) / 2;
yImgOffset = -(image.height - canvas.height) / 2;
drawRotated(0);
}
The drawRotated method is called right away to apply scaling changes. Along with that xImgOffset and yImgOffset are the difference in positions between the starting location of a horizontal and vertical canvas size in proportion to the original image dimensions.
Visually this looks something like this:
In the image above we are going to need to draw a horizontal image as the green horizontal rectangle when we draw it in our canvas. For vertical images the image is drawn with the width set to the canvas width and the height scaled proportionally with a offset so the image is centered. Likewise this is the same for horizontal images, we just need to keep in mind that we are drawing this as if the canvas is horizontal initially (See the first figure).
Finally the method as a whole looks like this:
function drawRotated(degrees){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.save();
ctx.translate(canvas.width/2,canvas.height/2);
ctx.rotate(degrees*Math.PI/180);
var angleToScale = degrees - 90;
var imgRatio = image.height/image.width;
if(imgRatio < 1) angleToScale += 90
if(angleToScale % 180 == 0)
ctx.scale(ratio, ratio);
ctx.translate(-canvas.width/2,-canvas.height/2);
ctx.drawImage(image, xImgOffset, yImgOffset, image.width, image.height);
ctx.restore();
}
Updated Fiddle For both horizontal and vertical images with original image ratio and cropping
This is setup to work with any canvas dimension and size.

How to detect where a stretched canvas was clicked?

I have a canvas that is stretched out since I am making a sandbox game. I can not use the normal method of detecting the pixel on the page a canvas is clicked because I need to know which stretch pixel was clicked. Hopefully this makes sense?
All you have to do is scale the position based on your current canvas size and original canvas size.
function scaleCursorPoint(int mouseX, int mouseY, ctx) {
return {
x: mouseX * (ctx.canvas.width / ctx.width),
y: mouseY * (ctx.canvas.height / ctx.height)
};
}
ctx (which is gotten with canvas.getContext('2d') has the width of the original unstretched. ctx.canvas gets the original canvas DOM element. ctx.canvas.width is the size of the DOM element (the stretched size).
Divide the two and you get the scale value. Then just multiple that scale value with the points you got and you're good

Zooming on HTML5 <canvas> and no pixelation for text?

Let's put some text on a HTML5 <canvas> with
var canvas = document.getElementById('myCanvas'),
ctx = canvas.getContext('2d'),
ctx.textBaseline = 'top';
ctx.textAlign = 'left';
ctx.font = '14px sans-serif';
ctx.fillText('Bonjour', 10, 10);
When zooming the canvas on text, one can see pixelation.
Is there a way of zooming on a canvas without having pixelation on text ?
When you fillText on the canvas, it stops being letters and starts being a letter-shaped collection of pixels. When you zoom in on it, the pixels become bigger. That's how a canvas works.
When you want the text to scale as a vector-based font and not as pixels, don't draw them on the canvas. You could create <span> HTML elements instead and place them on top of the canvas using CSS positioning. That way the rendering engine will render the fonts in a higher resolution when you zoom in and they will stay sharp. But anything you draw on the canvas will zoom accordingly.
Alternatively, you could override the browsers zoom feature and create your own zooming algorithm, but this will be some work.
When the user zooms in or out of the window, the window.onresize event handler is triggered. You can use this trigger to adjust the width and the height of the canvas css styling accordingly (not the properties of the canvas. That's the internal rendering resolution. Change the width and height attributes of the style which is the resolution it is scaled to on the website).
Now you effectively disabled the users web browser from resizing the canvas, and also have a place where you can react on the scaling input events. You can use this to adjust the context.scale of your canvas to change the size of everything you draw, including fonts.
Here is an example:
<!DOCTYPE html>
<html>
<head>
<script type="application/javascript">
"use strict"
var canvas;
var context;
function redraw() {
// clears the canvas and draws a text label
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
context.font = "60pt sans-serif";
context.fillText("Hello World!", 100, 100);
}
function adjustSize() {
var width = window.innerWidth;
var height = window.innerHeight;
// resize the canvas to fill the whole screen
var style = canvas.style;
style.width = width + "px";
style.height = height + "px";
// backup the old current scaling factor
context.save();
// change the scaling according to the new zoom factor
context.scale(1000 / width, 1000 / height);
// redraw the canvas
redraw();
// restore the original scaling (important because multiple calls to scale are relative to the current scale factor)
context.restore();
}
window.onload = function() {
canvas = document.getElementById("myCanvas");
context = canvas.getContext("2d");
adjustSize();
}
window.onresize = adjustSize;
</script>
</head>
<body>
<canvas id ="myCanvas" width = 1000 height = 1000 ></canvas>
</body>
</html>
If you only need to scale text you can simply scale the font size.
A couple of notes on that however: fonts, or typefaces, are not just straight forward to scale meaning you will not get a smooth progress. This is because fonts are often optimized for certain sizes so the sizes in between so to speak are a result of the previous and next size. This can make the font look like it's moving around a little when scaled up and is normal and expected.
The approach here uses a simply size scale. If you need an absolute smooth scale for animation purposes you will have to use a very different technique.
The simple way is:
ctx.font = (fontSize * scale).toFixed(0) + 'px sans-serif';
An online demo here.
For animation purposes you would need to do the following:
Render a bigger size to an off-screen canvas which is then used to draw the different sizes
When the difference is too big and you get problems with interpolation you will have to render several of these cached text images at key sizes so you can switch between them when scaling factor exceeds a certain threshold.
In this demo you can see that at small sizes the pixels gets a bit "clumpy" but otherwise is much smoother than a pure text approach.
This is because the browser uses bi-linear interpolation rather than bi-cubic with canvas (this may or may not change in the future) so it's not able to interpolate properly when the difference gets to big (see below for solution with this issue).
The opposite happens at big sizes as the text gets blurry also due to interpolation.
This is where we would have to switch to a smaller (or bigger) cached version which we then scale within a certain range before we again switch.
The demo is simplified to show only a single cached version. You can see halfway through that this works fine. The principle would be in a full solution (sizes being just examples):
(Update Here is a demo of a switched image during scale).
-- Cached image (100px)
-- Draw cached image above scaled based on zoom between 51-100 pixels
-- Cached image (50px) generated from 100px version / 2
-- Draw cached image above scaled based on zoom between 26-50 pixels
-- Cached image (25px) generated from 50px version / 2
-- Draw cached image above scaled based on zoom between 1-25 pixels
Then use a "sweet spot" (which you find by experiment a little) to toggle between the cached versions before drawing them to screen.
var ctx = canvas.getContext('2d'),
scale = 1, /// initial scale
initialFactor = 6, /// fixed reduction scale of cached image
sweetSpot = 1, /// threshold to switch the cached images
/// create two off-screen canvases
ocanvas = document.createElement('canvas'),
octx = ocanvas.getContext('2d'),
ocanvas2 = document.createElement('canvas'),
octx2 = ocanvas2.getContext('2d');
ocanvas.width = 800;
ocanvas.height = 150;
ocanvas2.width = 400; /// 50% here, but maybe 75% in your case
ocanvas2.height = 75; /// experiment to find ideal size..
/// draw a big version of text to first off-screen canvas
octx.textBaseline = 'top';
octx.font = '140px sans-serif';
octx.fillText('Cached text on canvas', 10, 10);
/// draw a reduced version of that to second (50%)
octx2.drawImage(ocanvas, 0, 0, 400, 75);
Now we only need to check the sweet spot value to find out when to switch between these versions:
function draw() {
/// calc dimensions
var w = ocanvas.width / initialFactor * scale,
h = ocanvas.height / initialFactor * scale;
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (scale >= sweetSpot) {
ctx.drawImage(ocanvas, 10, 10, w, h); /// use cached image 1
} else {
ctx.drawImage(ocanvas2, 10, 10, w, h); /// use cached image 2
}
}
So why not just draw the second cached image with a font? You can do that but then you are back to the issue with fonts being optimized for certain sizes and it would generate a small jump when scaling. If you can live with that then use this as it will provide a little better quality (specially at small sizes). If you need smooth animation you will have to reduce a larger cached version in order to keep the size 100% proportional.
You can see this answer on how to get a large image resized without interpolation problems.
Hope this helps.

html5 image cropping

I'm using context-blender to apply a multiply effect on the first 192 pixels of the html background-image with a fixed color to achieve a transparency effect on the header of the page.
On the html I have 2 canvas. One for the part of the image to apply the multiply effect and one for the color.
On the javascript, after setting the color of the color-canvas and the width of both canvas to the window.innerWidth I'm getting the background image with:
imageObj.src = $('html').css('background-image').replace(/^url|[\(\)]/g, '');
Now comes the problem. I want to draw a cropped image to the image to the image-canvas so I can apply the multiply effect. I'm trying to do the following:
imageObj.onload = function(){
// getting the background-image height
var imageHeight = window.innerWidth * imageObj.height / imageObj.width;
// get the corresponding pixels of the source image that correspond to the first 192 pixels of the background-image
var croppedHeight = 192 * imageObj.height / imageHeight;
// draw the image to the canvas
imageCanvas.drawImage(imageObj, 0, 0, imageObj.width, croppedHeight, 0, 0, window.innerWidth, 192);
// apply the multiply effect
colorCanvas.blendOnto( imageCanvas, 'multiply');
}
But I'm doing something wrong getting the cropped height.
Ex: For an 1536x1152 image and a 1293x679 browser container, the value I'm getting for the source cropped height is 230 but to get the correct crop I need to use something around 296.
Edit:
I'm using background-size: cover on the css to create the background-image
Edit2:
I created a fiddle to illustrate the problem. If you uncomment the line //cHeight *= magicConstant; the cropped image looks a lot better but things stop making sense. I removed the multiply effect on the fiddler but that's not required to reproduce the problem. I also noticed that the behavior changed if I remove the second canvas from the URL.
Btw, this behavior happened with google chrome, but I think the same thing happens on safari and firefox.
OK, I've fixed it. Man was that hard! Mainly because you forgot to set the imageCanvas' canvas height. It also didn't help that the image has a white border. I spent a hell of a lot of time trying to figure out where the padding was coming from.
So to start, for the case of the fiddle, in function doBlending(), set imageCanvas.canvas.height = height;
Then the calculations in crop() need to cover 2 possibilities. Is the image being scaled for height and truncated on the left or scaled for width and truncated on the bottom? I'm not going to write both for you, but here's the one for the case where it is scaled for height:
function crop(imageObj, imageCanvas, colorCanvas) {
// Assumes bg image is scaled for hight
var scale = imageObj.height / window.innerHeight;
var targetHeight = imageCanvas.canvas.height;
var targetWidth = window.innerWidth;
imageCanvas.drawImage(imageObj,
0, 0, targetWidth * scale, targetHeight * scale,
0, 0, targetWidth, targetHeight);
}
I really have no idea where you came up with the scaling factors in your example. The image is going to be scaled by multiplying both the x and y dimensions by some scale factor. That's how you preserve the aspect ratio. The scale factor will be the larger of the one to make the height of the image match the height of the window and the one to make the width of the image match the width of the window.
I think it may not be valid for you to be using window inner dimensions here. Since cover will maintain the aspect ratio of the background image it means that both of its dimensions may not be fully displayed. So if you are trying to transform between aspect ratios to determine where to clip, you would have to account for the fact that the image may flow out of the window borders.

Categories