Transparent Background not working - javascript

I'm making a small simple game in HTML and Javascript but I've run into an error. The sprite I've made on the "canvas" is not able to reach the canvas borders (the edge of the game world). After looking at it alot, I deduced it wasn't the code but the fact that the background of the image isn't transparent. But this makes no sense because the image file does have a transparent background.
How can I completely get rid of the background? Or is something in the code causing the sprite to have it's own border?
Image file:
What it looks like when run
http://prntscr.com/4btb32
Code:
// JavaScript Document
var canvasWidth = 800;
var canvasHeight = 600;
$('#gameCanvas').attr('width', canvasWidth);
$('#gameCanvas').attr('height', canvasHeight);
var keysDown = {};
$('body').bind('keydown', function(e){
keysDown[e.which] = true;
});
$('body').bind('keyup', function(e){
keysDown[e.which] = false;
});
var canvas = $('#gameCanvas')[0].getContext('2d');
var FPS = 30;
var image = new Image();
image.src = "ship.png";
var playerX = (canvasWidth/2) - (image.width/2);
var playerY = (canvasHeight/2) - (image.height/2);
setInterval(function() {
update();
draw();
}, 1000/FPS);
function update(){
if(keysDown[37]){
playerX -= 10;
}
if(keysDown[38]){
playerY -= 10;
}
if(keysDown[39]){
playerX += 10;
}
if(keysDown[40]){
playerY += 10;
}
playerX = clamp(playerX, 0, canvasWidth - image.width);
playerY = clamp(playerY, 0, canvasHeight - image.height);
}
function draw() {
canvas.clearRect(0,0, canvasWidth, canvasHeight);
canvas.strokeRect(0, 0, canvasWidth, canvasHeight);
canvas.drawImage(image, playerX, playerY);
}
function clamp(x, min, max){
return x < min ? min : (x > max ? max : x);
}
Thanks,
Ab

Your clamp function is making is so the x and y values are always between 0 and the canvas size minus the image size. This makes it so that your image will never leave the canvas (making the full image always on the canvas), and your image size is actually a bit larger than the ship image. The transparency of the image has nothing to do with the ship leaving the canvas, it has to do with the size. If you want to be able to have the ship leave the canvas, or get right next to the edge then decrease the size of the image to not have a border on it.
Alternatively you could have your clamp function clamp to 0 - image.width and canvasWidth + image.width (or height). This will allow the ship to fully disappear off the canvas.

Whether or not the sprite has a transparent background, the height and width of the image are still being used. you need to create your sprite in a way that the ship fills the entire canvas that you are painting it on.
The image will be as wide and tall as your background... so your ship wont reach the edge because there is still a background on your image that extend beyond your ship.

Related

HTML Canvas coordinate systems and rendering process

I'm playing with drawing on html canvas and I'm little confused of how different coordinate systems actually works. What I have learned so far is that there are more coordinate systems:
canvas coordinate system
css coordinate system
physical (display) coordinate system
So when I draw a line using CanvasRenderingContext2D
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(3, 1);
ctx.lineTo(3, 5);
ctx.stroke();
before drawing pixels to the display, the path needs to be
scaled according to the ctx transformation matrix (if any)
scaled according to the ratio between css canvas element dimensions (canvas.style.width and canvas.style.height) and canvas drawing dimensions (canvas.width and canvas.height)
scaled according to the window.devicePixelRatio (hi-res displays)
Now when I want to draw a crisp line, I found that there are two things to fight with. The first one is that canvas uses antialiasing. So when I draw a line of thikness 1 at integer coordinates, it will be blurred.
To fix this, it needs to be shifted by 0.5 pixels
ctx.moveTo(3.5, 1);
ctx.lineTo(3.5, 5);
The second thing to consider is window.devicePixelRatio. It is used to map logical css pixels to physical pixels. The snadard way how to adapt canvas to hi-res devices is to scale to the ratio
const ratio = window.devicePixelRatio || 1;
const clientBoundingRectangle = canvas.getBoundingClientRect();
canvas.width = clientBoundingRectangle.width * ratio;
canvas.height = clientBoundingRectangle.height * ratio;
const ctx = canvas.getContext('2d');
ctx.scale(ratio, ratio);
My question is, how is the solution of the antialiasing problem related to the scaling for the hi-res displays?
Let's say my display is hi-res and window.devicePixelRatio is 2.0. When I apply context scaling to adapt canvas to the hi-res display and want to draw the line with thickness of 1, can I just ignore the context scale and draw
ctx.moveTo(3.5, 1);
ctx.lineTo(3.5, 5);
which is in this case effectively
ctx.moveTo(7, 2);
ctx.lineTo(7, 10);
or do I have to consider the scaling ratio and use something like
ctx.moveTo(3.75, 1);
ctx.lineTo(3.75, 5);
to get the crisp line?
Antialiasing can occur both in the rendering on the canvas bitmap buffer, at the time you draw to it, and at the time it's displayed on the monitor, by CSS.
The 0.5px offset for straight lines works only for line widths that are odd integers. As you hinted to, it's so that the stroke, that can only be aligned to the center of the path, and thus will spread inside and outside of the actual path by half the line width, falls on full pixel coordinates. For a comprehensive explanation, see this previous answer of mine.
Scaling the canvas buffer to the monitor's pixel ratio works because on high-res devices, multiple physical dots will be used to cover a single px area. This allows to have more details e.g in texts, or other vector graphics. However, for bitmaps this means the browser has to "pretend" it was bigger in the first place. For instance a 100x100 image, rendered on a 2x monitor will have to be rendered as if it was a 200x200 image to have the same size as on a 1x monitor. During that scaling, the browser may yet again use antialiasing, or another scaling algorithm to "create" the missing pixels.
By directly scaling up the canvas by the pixel ratio, and scaling it down through CSS, we end up with an original bitmap that's the size it will be rendered, and there is no need for CSS to scale anything anymore.
But now, your canvas context is scaled by this pixel ratio too, and if we go back to our straight lines, still assuming a 2x monitor, the 0.5px offset now actually becomes a 1px offset, which is useless. A lineWidth of 1 will actually generate a 2px stroke, which doesn't need any offset.
So no, don't ignore the scaling when offsetting your context for straight lines.
But the best is probably to not use that offset trick at all, and instead use rect() calls and fill() if you want your lines to fit perfectly on pixels.
const canvas = document.querySelector("canvas");
// devicePixelRatio may not be accurate, see below
setCanvasSize(canvas);
function draw() {
const dPR = devicePixelRatio;
const ctx = canvas.getContext("2d");
// scale() with weird zoom levels may produce antialiasing
// So one might prefer to do the scaling of all coords manually:
const lineWidth = Math.round(1 * dPR);
const cellSize = Math.round(10 * dPR);
for (let x = cellSize; x < canvas.width; x += cellSize) {
ctx.rect(x, 0, lineWidth, canvas.height);
}
for (let y = cellSize; y < canvas.height; y += cellSize) {
ctx.rect(0, y, canvas.width, lineWidth);
}
ctx.fill();
}
function setCanvasSize(canvas) {
// We resize the canvas bitmap based on the size of the viewport
// while respecting the actual dPR
// Thanks to gman for the reminder of how to suppport all early impl.
// https://stackoverflow.com/a/65435847/3702797
const observer = new ResizeObserver(([entry]) => {
let width;
let height;
const dPR = devicePixelRatio;
if (entry.devicePixelContentBoxSize) {
width = entry.devicePixelContentBoxSize[0].inlineSize;
height = entry.devicePixelContentBoxSize[0].blockSize;
} else if (entry.contentBoxSize) {
if ( entry.contentBoxSize[0]) {
width = entry.contentBoxSize[0].inlineSize * dPR;
height = entry.contentBoxSize[0].blockSize * dPR;
} else {
width = entry.contentBoxSize.inlineSize * dPR;
height = entry.contentBoxSize.blockSize * dPR;
}
} else {
width = entry.contentRect.width * dPR;
height = entry.contentRect.height * dPR;
}
canvas.width = width;
canvas.height = height;
canvas.style.width = (width / dPR) + 'px';
canvas.style.height = (height / dPR) + 'px';
// we need to redraw
draw();
});
// observe the scrollbox size changes
try {
observer.observe(canvas, { box: 'device-pixel-content-box' });
}
catch(err) {
observer.observe(canvas, { box: 'content-box' });
}
}
canvas { width: 300px; height: 150px; }
<canvas></canvas>
Preventing anti-aliasing requires that the pixels of the canvas, which is a raster image, are aligned with the pixels of the screen, which can be done by multiplying the canvas size by the devicePixelRatio, while using the CSS size to hold the canvas to its original size:
canvas.width = pixelSize * window.devicePixelRatio;
canvas.height = pixelSize * window.devicePixelRatio;
canvas.style.width = pixelSize + 'px';
canvas.style.height = pixelSize + 'px';
You can then use scale on the context, so that the drawn images won't be shrunk by higher devicePixelRatios. Here I am rounding so that lines can be crisp on ratios that are not whole numbers:
let roundedScale = Math.round(window.devicePixelRatio);
context.scale(roundedScale, roundedScale);
The example then draws a vertical line from the center top of one pixel to the center top of another:
context.moveTo(100.5, 10);
context.lineTo(100.5, 190);
One thing to keep in mind is zooming. If you zoom in on the example, it will become anti-aliased as the browser scales up the raster image. If you then click run on the example again, it will become crisp again (on most browsers). This is because most browsers update the devicePixelRatio to include any zooming. If you are rendering in an animation loop while they are zooming, the rounding could cause some flickering.

How to position HTML Canvas fillRect() on the far right of a browser window on varying screen sizes

Basically, I have an animation of a sqaure travelling from right to left. Since I dont know what screen size I will be using to present my work, I cannot hard code a starting x-position value (e.g sqrx = 1400) for the starting position.If the animation is played on a larger screen to what I am developing on, the browser window will be much larger therefore the animation will start in the middle of the screen.
TLDR: Set the starting X position of right to left moving animation to snap to the right edge of a browser window no matter how big/small the browser window is.
var canvasWidth = c.width();
var canvasHeight= c.height();
//Vars to allow buttons to work for reset functionality
var playAnimation= true;
var startButton = $("#startAnimation");
var stopButton = $("#stopAnimation");
//Start position of Square
var sqrx = 1310;
//Animation timer
function animate(){
sqrx--;
ctx.clearRect(0,0,canvasWidth, canvasHeight);
ctx.fillRect(sqrx,700,40,50);
setTimeout(animate,33);
//save state when the cavnas is first drawn.
ctx.save();
};
animate();
it'll be a lot easier if you can give canvas, the width and height of window so that no matter which device you use it'll get the value of window screen size :
var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;
Give the rectangle value for its size :
var rect_size = 50;
This will make it easier for you to position the rectangle in the canvas.
function draw(){
ctx.beginPath();
ctx.fillStyle = "red";
ctx.fillRect(width - rect_size , 0 , rect_size , rect_size);
ctx.fill();
}
draw();

Canvas grid gets blurry at different zoom levels

I am trying to create a simple canvas grid which will fit itself to the player's current zoom level, but also to a certain canvas height/width proportional screen limit. Here is what I got so far:
JS:
var bw = window.innerWidth / 2; //canvas size before padding
var bh = window.innerHeight / 1.3; //canvas size before padding
//padding around grid, h and w
var pW = 30;
var pH = 2;
var lLimit = 0; //9 line limit for both height and width to create 8x8
//size of canvas - it will consist the padding around the grid from all sides + the grid itself. it's a total sum
var cw = bw + pW;
var ch = bh + pH;
var canvas = $('<canvas/>').attr({width: cw, height: ch}).appendTo('body');
var context = canvas.get(0).getContext("2d");
function drawBoard(){
for (var x = 0; lLimit <= 8; x += bw / 8) { //handling the height grid
context.moveTo(x, 0);
context.lineTo(x, bh);
lLimit++;
}
for (var x = 0; lLimit <= 17; x += bh / 8) { //handling the width grid
context.moveTo(0, x); //begin the line at this cord
context.lineTo(bw, x); //end the line at this cord
lLimit++;
}
//context.lineWidth = 0.5; what should I put here?
context.strokeStyle = "black";
context.stroke();
}
drawBoard();
Now, I succeeded at making the canvas to be at the same proportional level for each screen resolution zoom level. this is part of what I am trying to achieve. I also try to achieve thin lines, which will look the same at all different zooming levels, and of course to remove the blurriness. right now the thickness
of the lines change according to the zooming levels and are sometimes blurry.
Here is jsFiddle (although the jsFiddle window itself is small so you will barely notice the difference):
https://jsfiddle.net/wL60jo5n/
Help will be greatly appreciated.
To prevent blur, you should account for window.devicePixelRatio when setting dimensions of your canvas element (and account for that dimensions during subsequent drawing, of course).
width and height properties of your canvas element should contain values that are proportionally higher than values in CSS properties of the same names. This can be expressed e.g. as the following function:
function setCanvasSize(canvas, width, height) {
var ratio = window.devicePixelRatio,
style = canvas.style;
style.width = '' + (width / ratio) + 'px';
style.height = '' + (height / ratio) + 'px';
canvas.width = width;
canvas.height = height;
}
To remove blurry effect on canvas zoom/scale i used image-rendering: pixelated in css
The problem is that you are using decimal values to draw. Both the canvas width and the position increments in your drawBoard() loop use fractions. The canvas is a bitmap surface, not a vectorial drawing. When you set the width and height of the canvas, you set the actual number of pixels stored in memory. That value cannot be decimal (browsers will probably just trim the decimal part). When you try to draw at decimal positions, the canvas will use pixel interpolation to avoid aliasing, hence the occasional blur.
See a version where I round x before drawing:
https://jsfiddle.net/hts7yybm/
Try rounding the values just before you draw them, but not in your actual logic. That way, the imprecision won't stack as the algorithm keeps adding to the value.
function drawBoard(){
for (var x = 0; lLimit <= 8; x += bw / 8) {
var roundedX = Math.round(x);
context.moveTo(roundedX, 0);
context.lineTo(roundedX, bh);
lLimit++;
}
for (var x = 0; lLimit <= 17; x += bh / 8) {
var roundedX = Math.round(x);
context.moveTo(0, roundedX);
context.lineTo(bw, roundedX);
lLimit++;
}
context.lineWidth = 1; // never use decimals
context.strokeStyle = "black";
context.stroke();
}
EDIT: I'm pretty sure all browsers behave as if the canvas was an img element, so there's no way to prevent aliasing when the user zooms with their browser's zoom function, other than with prefixed css. And even then, I'm not sure the browsers's zoom feature takes that into account.
canvas {
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
-ms-interpolation-mode: nearest-neighbor;
}
Also, make sure the canvas doesn't have any CSS-set dimensions. That only stretches the image after it's been drawn instead of increasing the drawing surface. If you want to fill a block with the canvas by giving it 100% width and height, then you need some JS to compute the CSS-given height and width and set the value of the canvas's width and height property based on that. Then you can make your own implementation of a zoom function within your canvas drawing code, but depending on what you're doing it might be overkill.

How to increase the resolution of lines in canvas

window.onload=function(){
var c = document.getElementById('canvas'),
ctx = c.getContext('2d'),
x=0, y=0, cnt=1;
for(var i=0;i<(window.innerWidth)/10;i++){
ctx.moveTo(x, y); x+=5;
if(cnt%2){
y=5; cnt++;
ctx.lineTo(x, y);ctx.stroke();
}else{
y=0; cnt++;
ctx.lineTo(x, y);ctx.stroke();
}
}
}
<canvas id="canvas" style="width:100%; height:250px"></canvas>
If you run the above code then the resolution of lines in the zig-zag pattern in the if fine but in here you can see the image the resoultion of this pattern is very poor (please click on this image to view this problem):
what i have tried is that i have changed the condition (window.innerWidth)/10 to (winodw.innerWidth)/4 and x+=5 to x+=2
but what it does is that it makes the line so thick and bad that you don't want to see it.
so, what should i do to increase the resolution of the lines of the pattern?
Just make sure your canvas element is as big as you are displaying it.
i added c.width = windows.innerWidth and also c.heigth = 250 and the resolution looks correct now.
window.onload=function(){
var c = document.getElementById('canvas'),
ctx = c.getContext('2d'),
x=0, y=0, cnt=1;
c.width = window.innerWidth;
c.height = 250;
for(var i=0;i<(window.innerWidth);i++){
ctx.moveTo(x, y); x+=5;
if(cnt%2){
y=5; cnt++;
ctx.lineTo(x, y);ctx.stroke();
}else{
y=0; cnt++;
ctx.lineTo(x, y);ctx.stroke();
}
}
}
<canvas id="canvas" style="width:100%; height:250px"></canvas>
There are a couple of things, but mostly it comes down to this: you are drawing at a width of 100%, which is stretching the default size of a canvas you are drawing in - thats why it blurs. Set your width correctly using javascript and the sharpness increases. The only thing is, a difference of 5 pixels is barely noticeable, so you have to increase your size to something more... average. I have opted for 1/100 of the windows width, but you can turn it into anything.
// For safety, use event listeners and not global window method overwriting.
// It will become useful if you have multiple scripts you want to
// execute only after loading them!
window.addEventListener('DOMContentLoaded', function(){
var c = document.getElementById('canvas'),
ctx = c.getContext('2d'),
x = 0, y = 0;
// Set the correct width and height
c.width = window.innerWidth;
c.height = window.innerWidth / 100;
// Use moveTo once, then keep drawing from your previous lineTo call
ctx.moveTo(x, y);
// You only need your x value here, once we are off screen we can stop drawing and end the for loop!
for(; x < window.innerWidth; x += window.innerWidth / 100){
// Use lineTo to create a path in memory
// You can also see if your y needs to change because y = 0 = falsy
ctx.lineTo(x, (y = y ? 0 : window.innerWidth / 100));
}
// Call stroke() only once!
ctx.stroke();
// And for safety, call closePath() as stroke does not close it.
ctx.closePath();
}, false);
<canvas id="canvas"></canvas>
<!-- Remove all styling from the canvas! Do this computationally -->
maybe you can find your ans here
Full-screen Canvas is low res
basically it summarizes that instead of setting height and width in the css, you should set it via html (inside the canvas element via width and height attr) or via javaScript.
because when doing it in css, you are basically scaling it and thus reducing the resolution, so you have to mention the actual size in html element and not scale it in css.

Viewing transparent image and deciding where does the opacity starts and ends on X and Y coordinates

I have PNG image that is has a lot of white space that must be there. Let's say the image is 1000 by 1000 pixels and it has a black square 100 by 100 pixels located at 450px by 450px which is exactly in the center (the black square could be anywhere on the transparent image).
Now, is there a way to load the big image (the 1000x1000 pixel one) and search to find the X and Y position of the black square that is in the middle?
This should be cross browser compatible or at lest the major browser compatible.
The example on here: http://jsfiddle.net/cwolves/GaEeG/2/ shows how to see if the mouse is over the transparent part of an image. The problem is that I'm not sure how to scan with the code to get where transparency opacity starts from X and Y side of the image. It also seems to work only on Chrome.
var imgData,
width = 200,
height = 200;
$('#mask').bind('mousemove', function(ev){
if(!imgData){ initCanvas(); }
var imgPos = $(this).offset(),
mousePos = {x : ev.pageX - imgPos.left, y : ev.pageY - imgPos.top},
pixelPos = 4*(mousePos.x + height*mousePos.y),
alpha = imgData.data[pixelPos+3];
$('#opacity').text('Opacity = ' + ((100*alpha/255) << 0) + '%');
});
function initCanvas(){
var canvas = $('<canvas width="'+width+'" height="'+height+'" />')[0],
ctx = canvas.getContext('2d');
ctx.drawImage($('#mask')[0], 0, 0);
imgData = ctx.getImageData(0, 0, width, height);
}
I can only use PHP or JavaScript/plugins to do this.
I hope there's a way to do this. Thanks.

Categories