i have following problem. Im trying to draw a border with notches on my buttons and slider. thats working fine. but i noticed that my diagonal lines are twice as thick as the normal ones.
I saw that i have to work on the width/height settings in the canvas/tag itself. but i cant get it working!
Maybe i dont understand realy to make it work. pls help :)
button_canvas_border
slider_canvas_border
thats my html
<canvas width="1290" height="738" class="slider_canvas" id="slider_canvas" ></canvas>
or
<canvas class="slider_canvas" id="slider_canvas" ></canvas>
both dont work
and my js
jQuery(document).ready(function($) {
$('.slider_canvas').each(function(index, canvas) {
var wrapper_height = $('.nsaw_slider_front_wrapper').height() + 30;
var wrapper_width = $('.nsaw_slider_front_wrapper').width() + 30;
/* doesnt matter what i do on the below, its just not working */
canvas.width = wrapper_width;
canvas.height = wrapper_height;
canvas.style.width = wrapper_width;
canvas.style.height = wrapper_height;
canvas.setAttribute('width', wrapper_width);
canvas.setAttribute('height', wrapper_height);
/* doesnt matter what i do on the above, its just not working */
var point_01_cord = wrapper_width * 0.85;
var point_02_cord = wrapper_height * 0.25423;
var point_03_cord = wrapper_width * 0.15;
var point_04_cord = wrapper_height * 0.74577;
var ctx = canvas.getContext('2d');
var gradient = ctx.createLinearGradient(0, 0, 170, 0);
gradient.addColorStop("0", "#0033ff");
gradient.addColorStop("1" ,"#ffff00");
ctx.strokeStyle = gradient;
ctx.lineWidth = 5;
ctx.beginPath();
ctx.translate(0.5,0.5);
ctx.moveTo(0,0);
ctx.lineTo(point_01_cord,0);
ctx.lineTo(wrapper_width,point_02_cord);
ctx.lineTo(wrapper_width,wrapper_height);
ctx.lineTo(point_03_cord,wrapper_height);
ctx.lineTo(0,point_04_cord);
ctx.lineTo(0,0);
ctx.stroke();
});
});
maybe someone can help :)
Strokes do overlap from both sides of the coordinates, that's by the way why you saw some say you should translate by 0.5, so that lineWidth = 1 covers a full pixel instead of two halves. (More on that here).
Here you are drawing a 5px wide line so you really don't want that offset (5 pixels can be rendered perfectly fine), even though it's not your actual problem here.
Your drawing is at the edges of the canvas boundaries. This means that only half of your line is visible.
To workaround that, offset all your lines coordinates to take into account its lineWidth. For instance the top line instead of having its y values set to 0 should have it set to 2.5 without the initial translate or to 2 with if you really want to keep it.
Related
I'm trying to make a pixel editor with 2 canvas. The first canvas displays a second canvas which contains the pixels. The first canvas uses drawImage to position and scale the second canvas.
When the second canvas is scaled smaller than it's original size, it starts to glitch.
Here is the canvas displayed at it's original size. When I zoom in, the second canvas get bigger and everything works perfectly.
However when I zoom out, the grid and the background (transparency) act very strangely.
To draw the second canvas on the first canvas, I use the function
ctx.drawImage(drawCanvas, offset.x, offset.y, width * pixelSize, height * pixelSize);
I have read that scaling in multiple iterations might give a better quality with images but I am not sure about a canvas.
I could fully redraw the second canvas in a lower resolution when the user zooms out, but it is a bit heavy on the cpu.
Is there any better solution that I don't know of?
Your problem comes from anti-aliasing.
Pixels aren't sub-divisible, and when you ask the computer to draw something outside of the pixel boundaries, it will try its best to render something that usually looks good to eyes, by mixing the colors so that what should have been a black 0.1 pixel line will become a light-gray pixel for instance.
This generally works good, particularly with pictures of the real word, or complex shapes like circles. However with grids... That's not so great as you experienced it.
Your case is dealing with two different cases, and you will have to deal with hem separately.
In the canvas 2D API (and a lot of 2D APIs) stroke do bleed from both sides of the coordinates you did set it. So when drawing lines of 1px wide, you need to account for a 0.5px offset to be sure it won't get rendered as two gray pixels. For more info about this, see this answer. You are probably using such a stroke for the grid.
fill on the other hand only covers the inside of the shape, so if you fill a rectangle, you need to not offset its coords from the px boundaries. This is required for the checkerboard.
Now, for boh these drawings, the best is probably to use patterns. You only need to draw a small version of it, and then the pattern will repeat it automatically, saving a lot of computation.
Scaling of a pattern can be done by calling the transform methods of the 2D context. We can even take advantage of the closest-neighbor algorithm to avoid antialising when drawing this pattern by setting the imageSmoothingEnabled property to false.
However for our grid, we may want to keep the lineWidth constant. For this we will need to generate a new pattern at every draw call.
// An helper function to create CanvasPatterns
// returns a 2DContext on which a simple `finalize` method is attached
// method which does return a CanvasPattern from the underlying canvas
function patternMaker(width, height) {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.finalize = (repetition = "repeat") => ctx.createPattern(canvas, repetition);
return ctx;
}
// The checkerboard can be generated only once
const checkerboard_patt_maker = patternMaker(2, 2);
checkerboard_patt_maker.fillStyle = "#CCC";
checkerboard_patt_maker.fillRect(0,0,1,1);
checkerboard_patt_maker.fillRect(1,1,1,1);
const checkerboard_patt = checkerboard_patt_maker.finalize();
// An helper function to create grid patterns
// Since we want a constant lineWidth, no matter the zoom level
function makeGridPattern(width, height) {
width = Math.round(width);
height = Math.round(height);
const grid_patt_maker = patternMaker(width, height);
grid_patt_maker.lineWidth = 1;
// apply the 0.5 offset only if we are on integer coords
// for instance a <3,3> pattern wouldn't need any offset, 1.5 is already perfect
const x = width/2 % 1 ? width/2 : width/2 + 0.5;
const y = height/2 % 1 ? height/2 : height/2 + 0.5;
grid_patt_maker.moveTo(x, 0);
grid_patt_maker.lineTo(x, height);
grid_patt_maker.moveTo(0, y);
grid_patt_maker.lineTo(width, y);
grid_patt_maker.stroke();
return grid_patt_maker.finalize();
}
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const checkerboard_input = document.getElementById('checkerboard_input');
const grid_input = document.getElementById('grid_input');
const connector = document.getElementById('connector');
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const checkerboard_zoom = checkerboard_input.value;
const grid_zoom = grid_input.value;
// we generate a new pattern for the grid, so the lineWidth is always 1
const grid_patt = makeGridPattern(grid_zoom, grid_zoom);
// draw once the rectangle covering the whole canvas
// with normal transforms
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height);
// the checkerboard
ctx.fillStyle = checkerboard_patt;
// our path is already drawn, we can control only the fill
ctx.scale(checkerboard_zoom, checkerboard_zoom);
// avoid antialiasing when painting our pattern (similar to rounding the zoom level)
ctx.imageSmoothingEnabled = false;
ctx.fill();
// done, reset to normal
ctx.imageSmoothingEnabled = true;
ctx.setTransform(1, 0, 0, 1, 0, 0);
// paint the grid
ctx.fillStyle = grid_patt;
// because our grid is drawn in the middle of the pattern
ctx.translate(Math.round(grid_zoom/2), Math.round(grid_zoom/2));
ctx.fill();
// reset
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
draw();
checkerboard_input.oninput = grid_input.oninput = function(e) {
if(connector.checked) {
checkerboard_input.value = grid_input.value = this.value;
}
draw();
};
connector.oninput = e => checkerboard_input.oninput();
<label>checkerboard-layer zoom<input id="checkerboard_input" type="range" min="2" max="50" step="0.1"></label><br>
<label>grid-layer zoom<input id="grid_input" type="range" min="2" max="50" step="1"></label><br>
<label>connect both zooms<input id="connector" type="checkbox"></label>
<canvas id="canvas"></canvas>
I have been looking around for this function and thus far I just can't find any I can make any sense of. I already have a rotating function to make it equal to the position but slowly is proving to be a bit harder with 0-360 and all.
I am using a html canvas 2d context to render the objects on a Cartesian coordinate system .
I would like object1 to face at positionX and positionY at a turn rate (R) , fairly straightforward.
there is no need for me to supply any code since your likely going to make your own anyways. But I will anyways here you go:
let faceAt = function (thisObject,positionX,positionY) {
let desiredLocationX = positionX - thisObject.transform.x;
let desiredLocationY = positionY -thisObject.transform.y;
thisObject.transform.rotation = Math.degrees(Math.atan2(desiredLocationY, desiredLocationX));
};
The (Math.degrees) function converts radians to degrees.
This thread says it all : https://www.google.ca/amp/s/jibransyed.wordpress.com/2013/09/05/game-maker-gradually-rotating-an-object-towards-a-target/amp/
This question is quite unclear. But, I'm assuming you essentially just want to rotate an element around an arbitrary point on a HTML5 canvas.
On a canvas, you can only draw one element at a time. You can't really manipulate singular elements - for example, you can't rotate an element by itself. Instead, you'd need to rotate the entire canvas. This will always rotate around the centre of the canvas, but if you move the canvas origin, then you will draw on a different part of the canvas; thus allowing you to rotate around a point.
Check out the following example. You can click anywhere on the canvas to make the square rotate around that point. Hopefully this is what you are after:
let cv = document.getElementById("cv");
let ctx = cv.getContext("2d");
let angle = 0;
//Variables you can change:
let speed = 1; //Degrees to rotate per frame
let pointX = 250; //The x-coord to rotate around
let pointY = 250; //The y-coord to rotate around
ctx.fillStyle = "#000";
setInterval(()=>{ //This code runs every 40ms; so that the animation looks smooth
angle = (angle + speed) % 360; //Increment the angle. Bigger changes here mean that the element will rotate faster. If we go over 360deg, reset back to 0.
ctx.clearRect(0, 0, 400, 400); //Clear away the previous frame.
//Draw the point we are rotating around
ctx.beginPath();
ctx.arc(pointX,pointY,5,0,2*Math.PI);
ctx.fill();
ctx.closePath();
ctx.save(); //Save the state before we transform and rotate the canvas; so we can go back to the unrotated canvas for the next frame
ctx.translate(pointX, pointY); //Move the origin (0, 0) point of the canvas to the point to rotate around. The canvas always rotates around the origin; so this will allow us to rotate around that point
ctx.rotate(angle*Math.PI/180); //Rotate the canvas by the current angle. You can use your Math.degrees function to convert between rads / degs here.
ctx.fillStyle = "#f00"; //Draw in red. This is also restored when ctx.restore() is called; hence the point will always be black; and the square will always be red.
ctx.fillRect(0, 0, 50, 50); //Draw the item we want rotated. You can draw anything here; I just draw a square.
ctx.restore(); //Restore the canvas state
}, 40);
//Boring event handler stuff
//Move the point to where the user clicked
//Not too robust; relys on the body padding not changing
//Really just for the demo
cv.addEventListener("click", (event)=>{
pointX = event.clientX - 10;
pointY = event.clientY - 10;
});
#cv {
border:solid 1px #000; /*Just so we can see the bounds of the canvas*/
padding:0;
margin:0;
}
body {
padding:10px;
margin:0;
}
<canvas id="cv" width="400" height="400"></canvas><br>
Click on the canvas above to make the rectangle rotate around the point that was clicked.
I'm drawing a line chart with canvas. The chart is responsive, but the line has to have a fixed width.
I made it responsive with css
#myCanvas{
width: 80%;
}
,so the stroke is scaled.
The only solution I have found is to get the value of the lineWidth with the proportion between the width attribute of the canvas and its real width.
To apply it, I clear and draw the canvas on resize.
<canvas id="myCanvas" width="510" height="210"></canvas>
<script type="text/javascript">
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
function draw(){
var canvasattrwidth = $('#myCanvas').attr('width');
var canvasrealwidth = $('#myCanvas').width();
// n sets the line width
var n = 4;
var widthStroke = n * (canvasattrwidth / canvasrealwidth) ;
ctx.lineWidth = widthStroke;
ctx.beginPath();
ctx.moveTo( 0 , 10 );
ctx.lineTo( 200 , 100 );
ctx.stroke();
}
$(window).on('resize', function(){
ctx.clearRect(0, 0, c.width, c.height);
draw();
});
draw();
</script>
This is my first canvas and I think there is an easier way to made the lineWidth fixed (not to clear and draw everytime on resize), but I cannot find it.
There is a question with the similar problem
html5 canvas prevent linewidth scaling
but with the method scale(), so I cannot use that solution.
There is no way to get a real world dimension of details for the canvas such as millimeters or inches so you will have to do it in pixels.
As the canvas resolution decreases the pixel width of a line needs to decrease as well. The limiting property of line width is a pixel. Rendering a line narrower than a pixel will only approximate the appearance of a narrower line by reducing the opacity (this is done automatically)
You need to define the line width in terms of the lowest resolution you will expect, within reason of course and adjust that width as the canvas resolution changes in relation to this selected ideal resolution.
If you are scaling the chart by different amounts in the x and y directions you will have to use the ctx.scale or ctx.setTransform methods. As you say you do not want to do this I will assume that your scaling is always with a square aspect.
So we can pick the lowest acceptable resolution. Say 512 pixels for either width or height of the canvas and select the lineWidth in pixels for that resolution.
Thus we can create two constants
const NATIVE_RES = 512; // the minimum resolution we reasonably expect
const LINE_WIDTH = 1; // pixel width of the line at that resolution
// Note I Capitalize constants, This is non standard in Javascript
Then to calculate the actual line width is simply the actual canvas.width divided by the NATIVE_RES then multiply that result by the LINE_WIDTH.
var actualLineWidth = LINE_WIDTH * (canvas.width / NATIVE_RES);
ctx.lineWidth = actualLineWidth;
You may want to limit that size to the smallest canvas dimension. You can do that with Math.min or you can limit it in the largest dimension with Math.max
For min dimention.
var actualLineWidth = LINE_WIDTH * (Math.min(canvas.width, canvas.height) / NATIVE_RES);
ctx.lineWidth = actualLineWidth;
For max dimension
var actualLineWidth = LINE_WIDTH * (Math.max(canvas.width, canvas.height) / NATIVE_RES);
ctx.lineWidth = actualLineWidth;
You could also consider the diagonal as the adjusting factor that would incorporate the best of both x and y dimensions.
// get the diagonal resolution
var diagonalRes = Math.sqrt(canvas.width * canvas.width + canvas.height * canvas.height)
var actualLineWidth = LINE_WIDTH * (diagonalRes / NATIVE_RES);
ctx.lineWidth = actualLineWidth;
And finally you may wish to limit the lower range of the line to stop strange artifacts when the line gets smaller than 1 pixel.
Set lower limit using the diagonal
var diagonalRes = Math.sqrt(canvas.width * canvas.width + canvas.height * canvas.height)
var actualLineWidth = Math.max(1, LINE_WIDTH * (diagonalRes / NATIVE_RES));
ctx.lineWidth = actualLineWidth;
This will create a responsive line width that will not go under 1 pixel if the canvas diagonal resolution goes under 512.
The method you use is up to you. Try them out a see what you like best. The NATIVE_RES I picked "512" is also arbitrary and can be what ever you wish. You will just have to experiment with the values and method to see which you like best.
If your scaling aspect is changing then there is a completely different technique to solve that problem which I will leave for another question.
Hope this has helped.
I am experimenting with drawing using javascript and the canvas element..my goal now is to draw a circle and gradually increase the opacity; I have this code:
http://codepen.io/anon/pen/zrVvOQ
Which seems to work, but the circle has rough edges; I found I need to clear the canvas each time the frame is redrawn, but the attempts I have made have not quite worked...any suggestions on how to?
window.onload = function draw(){
var frame1 = document.getElementById('frame1');
if (frame1.getContext){
var ctx = frame1.getContext('2d');
var centerX = frame1.width / 2;
var centerY = frame1.height / 2;
var radius = 50;
var alpha = 1.0;
/*call function over and over */
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
var rendergreen = function()
{
var opacityValue = 0;
opacityValue += 0.03;
ctx.fillStyle = 'rgba(68,107,62, ' + opacityValue + ')';
animate();
ctx.arc(50, centerY, radius, 0, 2 * Math.PI, false);
ctx.clip();
ctx.fill();
ctx.closePath;
function animate() {
if (opacityValue < 1) {
opacityValue += 0.3;
} else {
opacityValue = 1;
}
}
requestAnimationFrame(rendergreen);
}
rendergreen();
}
}
You say the circle has "rough edges". That's pixellation and is inherent in using canvas to draw, which is a bit-mapped style of graphics. That means that you essentially can't get higher resolution than a single pixel. Contrast that with svg which is vector-based. An svg image can be magnified a thousand times and still have a smooth edge. I've shown an svg circle next to the canvas circle so that you can see the difference. It becomes much more apparent if you zoom in with your browser. There are pro's and con's to using canvas vs svg, too much to go into here, but it's worth looking into if you're really concerned.
In terms of changing the opacity of the circle, you've got several problems with your approach. With the way you've written it, you're actually not changing the opacity. Instead, you're drawing the same very transparent circle many times over top of each other so that by the end it looks opaque, giving the impression that you are gradually increasing the transparency of a single circle. Notice that you're setting your opacity to zero in each drawing iteration, then incrementing it to 0.05 (note that there are differences in the code in your question versus in the codepen that you linked to...I'm referring to the codepen version), then drawing it (so it will always be drawn at opacity 0.05), then further changing the value of the variable opacityValue which is never used in the drawing. The example below shows a relatively simple example of what I think you were trying to achieve. Note that I've deliberately made the 'clearRect' too small so that you can see how not clearing the canvas each time allows semi-transparent drawings to "pile up". This also allows you to see that the blockiness gets worse if you overlay many semi-transparent images. e.g. Compare the left and right sides of the canvas circle. The part of the circle that is cleared every time ends up looking smoother because of anti-aliasing, but the overlaid images have the smoothing effects of anti-aliasing effectively destroyed.
window.onload = function draw() {
var frame1 = document.getElementById('frame1');
if (frame1.getContext) {
var ctx = frame1.getContext('2d');
var opacityValue = 0;
var render = function() {
ctx.clearRect(0, 0, 80, 80); // deliberately set too small
ctx.beginPath();
opacityValue += 0.01;
ctx.fillStyle = 'rgba(68,107,62, ' + opacityValue + ')';
ctx.arc(60, 60, 50, 0, 2 * Math.PI, false);
ctx.fill();
ctx.closePath;
requestAnimationFrame(render);
}
render();
}
}
<canvas id="frame1" width="120" height="120"></canvas>
<svg width="120" height="120">
<circle cx="60" cy="60" r="50" fill="#446B3E"></circle>
</svg>
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.