HTML5 canvas lines are blury in the arc() function - javascript

I am having trouble with blurry lines on the element.
ctx.moveTo(2,2);
ctx.lineTo(50,2);
ctx.arc(27,2,25,0,3.1415926);
ctx.stroke();
I tried making the linewidth 0.5 but that didn't fix it. Everything I try doesn't seem to do anything.
The result turns out very pixely.
See result on https://rawgit.com/Mythius/uploads/master/Platformer.html
If anyone knows how to fix this please let me know.

Do not set your canvas size in CSS alone.
The displayed size of the canvas can be changed using a stylesheet. The image is scaled during rendering to fit the styled size. If your renderings seem distorted, try specifying your width and height attributes explicitly in the attributes, and not using CSS.
Default canvas size
var canvas = document.getElementById('cnvs');
var ctx = canvas.getContext('2d');
ctx.lineWidth = 1;
ctx.translate(0.5, 0.5);
draw();
function draw() {
ctx.moveTo(0, 0);
ctx.lineTo(50, 0);
ctx.arc(25, 0, 25, 0, Math.PI);
ctx.stroke();
}
body {
background-color: #aaa;
}
#cnvs {
background-color: #fff;
border: 1px solid #000;
}
<canvas id="cnvs"></canvas>
With size specified on the canvas element attributes
var canvas = document.getElementById('cnvs');
var ctx = canvas.getContext('2d');
ctx.lineWidth = 1;
ctx.translate(0.5, 0.5);
draw();
function draw() {
ctx.moveTo(0, 0);
ctx.lineTo(50, 0);
ctx.arc(25, 0, 25, 0, Math.PI);
ctx.stroke();
}
body {
background-color: #aaa;
}
#cnvs {
width: 500px;
height: 500px;
background-color: #fff;
border: 1px solid #000;
}
<canvas id="cnvs" width="500" height="500"></canvas>

As an addendum to Sébastien's answer: The 'blockiness' of a canvas image sits at the confluence of screen resolution, canvas dimensions, and styling properties. Depending on these factors an image can appear more or less blocky/sharp - but there's only so much you can do.
Some people say that drawing to a larger canvas and styling it proportionally smaller improves the appearance of fine detail -- by the principle that small images made large look block so large images made small will look less blocky -- while others are not convinced.
Below is a snippet which draws the same content onto four canvases at two different sizes and resolutions, but all canvas are styled to the same on-screen dimensions. How blocky are they on your gear? To me they look pretty much the same, but when I save them I do notice a difference.
(function(doc) {
function $(a) {
switch (a.slice(0, 1)) {
case "#":
return doc.getElementById(a.slice(1));
case ".":
return [].slice.call(doc.getElementsByClassName(a.slice(1)));
}
}
function save(e) {
var cnv = $(e.target.getAttribute('data-canvas')),
lnx = $('#savelink');
lnx.href = cnv.toDataURL();
lnx.download = e.target.getAttribute('data-res') +
'_ ' + cnv.width + 'x' +
cnv.height + '.png';
lnx.click();
}
function lowRes(cnv, ctx) {
var img = new Image;
img.addEventListener('load', function() {
ctx.clearRect(0, 0, cnv.width, cnv.height);
ctx.drawImage(this, 0, 0);
});
img.src = cnv.toDataURL('image/jpeg', 0.64);
};
function draw(id, wh, lw, res) {
var cnv = $(id),
ctx = cnv.getContext('2d'),
xy = wh / 2,
fc = 8,
shrink = (xy * 0.9) / fc,
flag = !1;
cnv.width = wh,
cnv.height = wh,
ctx.lineWidth = lw;
ctx.fillStyle = '#eee';
ctx.fillRect(0,0,cnv.width,cnv.height);
ctx.beginPath();
ctx.moveTo(0, xy);
ctx.lineTo(cnv.width, xy);
while (--fc) {
ctx.arc(xy, xy, shrink * fc, 0, Math.PI, flag);
flag = !flag;
}
ctx.stroke();
ctx.fillStyle = '#777';
ctx.font = Math.round(cnv.height * 0.025) + 'px serif';
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
ctx.beginPath();
ctx.fillText(
('lineWidth = ' + lw +
', width/height = ' + wh + 'px, ' +
(res ? 'low-res' : 'hi-res')),
Math.round(cnv.width * 0.9),
Math.round(cnv.height * 0.96)
);
res && lowRes(cnv, ctx);
}
doc.addEventListener('DOMContentLoaded', function() {
[
['#c1', 500, 1, !1],
['#c2', 1500, 3, !1],
['#c3', 500, 1, !0],
['#c4', 1500, 3, !0]
].forEach(function(n) {
draw.apply(null, n);
});
$('.save').forEach(function(n) {
n.addEventListener('click', save, !1);
});
}, false);
}(document));
.ch {
position:relative;
width:500px;
height:500px;
border:1px solid #999;
margin:2rem auto;
}
.ch canvas {
position:absolute;
top:0;
left:0;
width:100%;
height:100%;
display:block;
}
.ch .save {
position:absolute;
top:2%;
left:2%;
color:#aaa;
font-size:2rem;
font-weight:600;
cursor:pointer;
display:inline-block;
transition:color 333ms;
}
.ch .save:hover {
color:#000;
}
<div class="ch">
<canvas id="c1"></canvas>
<div class="save" title=" Save Image " data-canvas="#c1" data-res="hi">⇩</div>
</div>
<div class="ch">
<canvas id="c2"></canvas>
<div class="save" title=" Save Image " data-canvas="#c2" data-res="hi">⇩</div>
</div>
<div class="ch">
<canvas id="c3"></canvas>
<div class="save" title=" Save Image " data-canvas="#c3" data-res="lo">⇩</div>
</div>
<div class="ch">
<canvas id="c4"></canvas>
<div class="save" title=" Save Image " data-canvas="#c4" data-res="lo">⇩</div>
</div>
<a id="savelink" href="" download="" target="_blank"></a>

Related

Downloading canvas with border sytle

I want to download canvas with border. On screen Canvas has border but when I download canvas, its border doesnt appear
let canvas=qrRef.current.querySelector("canvas")
canvas.style.border="black 30px solid"
if(type=="PNG"){
let image=canvas.toDataURL("image/png")
let anchor=document.createElement("a");
anchor.href=image;
anchor.download=`qr-pollective.png`
document.body.appendChild(anchor);
anchor.click()
document.body.removeChild(anchor)
}
console.log(canvas)
<canvas height="400" width="400" style="border: 30px solid black; border-radius: 5px; border-collapse: collapse;">
What you can do is create a bigger canvas which has space for the black border.
Then copy the canvas you have created onto this bigger one, offset by 30px to make room for the border.
Then draw the 4 black rectangles that make up the border. This bigger canvas is then the one that you want to download.
const canvas = document.querySelector('canvas');
const biggerCanvas = document.createElement('canvas');
const w = canvas.width + 60;
const h = canvas.height + 60;
biggerCanvas.width = w;
biggerCanvas.height = h;
const ctx = biggerCanvas.getContext('2d');
ctx.drawImage(canvas, 30, 30);
ctx.beginPath();
ctx.rect(0, 0, w, 30);
ctx.fillStyle = "black";
ctx.fill();
ctx.rect(0, 0, 30, h);
ctx.fill();
ctx.rect(w - 30, 0, w - 30, h);
ctx.fill();
ctx.rect(0, h - 30, w, h);
ctx.fill();
document.body.append(biggerCanvas); //just to show it has drawn the border
<canvas height="400" width="400" style="border: 30px solid black; border-radius: 5px; border-collapse: collapse;">
I solved my problem with html2canvas here is my code
html2canvas(canvas,{backgroundColor: "transparent"}).then(function (canvas) {
var myImage = canvas.toDataURL();
downloadURI(myImage, `qr-pollective.png`);
});
function downloadURI(uri, name) {
var link = document.createElement("a");
link.download = name;
link.href = uri;
document.body.appendChild(link);
link.click();
}

How can I create a perspective effect within a canvas?

I know how to create a perspective effect in vanilla CSS, but how can I create this effect in a canvas?
.scene {
width: 200px;
height: 200px;
border: 2px solid black;
margin: 40px;
}
.panel {
width: 100%;
height: 100%;
background: red;
/* perspective function in transform property */
transform: perspective(600px) rotateY(45deg);
}
<div class="scene">
<div class="panel"></div>
</div>
I tried the setTransform() method without sucess.
function drawScene(margin, size) {
ctx.strokeStyle = "black";
ctx.strokeRect(margin, margin, size, size);
}
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var margin = 100;
var size = 200;
drawScene(margin, size)
ctx.setTransform(1, -0.1, 0, 1, -10, 0);
//ctx.rotate(1 * Math.PI / 180); how to rotateY
ctx.fillStyle = "red";
ctx.fillRect(margin, margin, size, size);
ctx.fillStyle = 'black';
ctx.font = "48px Courier";
ctx.fillText("hello", margin, size);
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #d3d3d3;"></canvas>
I tried both solution from HTML Canvas: Rotate the Image 3D effect but none nailed it. The perspective effet isnt here.
You need create a path by points matrix (x1, y1; x2, y2; x3, y3; x4, y4). Apply a transform matrix in your point matrix. After, print on canvas with path.
1: You need study by tranformation matrix (http://docdingle.com/teaching/cs545/presents/p12b_cs545_WarpsP2.pdf)
2:In Summary you have a transformation' matrix to translading, space, rotate or apply perspective deformation:
//this is a tranformation matrix to percpective deformation A and B are values that apply deformation
matrix_tranf = [ [1, 0 ,0] , [0,1,0] , [ A, B, 1] ];
In my code I left any examples
for(let i = 0; i < 4; i++){
//u
x1 = matrix[i][0]
//v
y1 = matrix[i][1]
w = matrix_tranf[2][0] * x1 + matrix_tranf[2][1] * y1 + 1;
v = [ ( x1 / w ) , ( y1 / w ) ];
matrix[i] = v;
}
Look
https://codepen.io/Luis4raujo/pen/wvoqEwg
If this answer help you, check as correct or voteup!

Change canvas background color to transparent

I have a text under canvas, i want to show it when i am erasing background color of canvas. Now it is red, when i wrote transparent it does not work. I need to show that text when i draw with the mouse. I tried with rgba too, but is was not working.
please help me if you can
enter code here
var cont = document.getElementById("spots"), // UI elements
canvas = document.getElementById("canvas"),
alpha = document.getElementById("alpha"),
modes = document.getElementById("modes"),
ctx = canvas.getContext("2d"),
isDown = false, // defaults
color = "red";
// set up color palette using a custom "Spot" object
// This will use a callback function when it is clicked, to
// change current color
function Spot(color, cont, callback) {
var div = document.createElement("div");
div.style.cssText = "width:50px;height:50px;border:1px solid #000;margin:0 1px 1px 0;background:" + color;
div.onclick = function() {callback(color)};
cont.appendChild(div);
}
// add some spot colors to our palette container
new Spot(color, cont, setColor);
// this will set current fill style based on spot clicked
function setColor(c) {ctx.fillStyle = c}
// setup defaults
ctx.fillStyle = color;
ctx.globalAlpha = 1;
// events
canvas.onmousedown = function() {isDown = true};
window.onmouseup = function() {isDown = false};
window.onmousemove = function(e) {
if (!isDown) return;
var r = canvas.getBoundingClientRect(),
x = e.clientX - r.left,
y = e.clientY - r.top;
ctx.beginPath();
ctx.arc(x, y, 25, 0, 2*Math.PI);
ctx.fill();
};
.main-content{
position: relative;
width: 300px;
height: 300px;
}
.main-text{
position: absolute;
right: 0;
left: 0;
text-align: center;
z-index: 8;
font-size: 35px;
}
#canvas{
background-color: green;
position: absolute;
z-index: 9;
}
<div class="main-content">
<p class="main-text">You Won!!!</p>
<canvas id="canvas" width=300 height=300 style="border: 1px solid green;"></canvas>
<div id="spots"></div>
</div>
I think you would like to get something like the solution in the snippet below.
var cont = document.getElementById("spots"), // UI elements
canvas = document.getElementById("canvas"),
alpha = document.getElementById("alpha"),
modes = document.getElementById("modes"),
ctx = canvas.getContext("2d"),
isDown = false, // defaults
color = "green";
// set up color palette using a custom "Spot" object
// This will use a callback function when it is clicked, to
// change current color
function Spot(color, cont, callback) {
var div = document.createElement("div");
div.style.cssText = "width:50px;height:50px;border:1px solid #000;margin:0 1px 1px 0;background:" + color;
div.onclick = function() {
callback(color)
};
cont.appendChild(div);
}
// add some spot colors to our palette container
new Spot(color, cont, setColor);
// this will set current fill style based on spot clicked
function setColor(c) {
ctx.fillStyle = c
}
// setup defaults
ctx.fillStyle = color;
ctx.globalAlpha = 1;
// create a rectangle using canvas functions, not CSS
// background color.
const createRect = (ctx, width, height) => {
ctx.fillRect(0, 0, width, height)
}
createRect(ctx, 300, 300)
// events
canvas.onmousedown = function() {
isDown = true
};
window.onmouseup = function() {
isDown = false
};
window.onmousemove = function(e) {
if (!isDown) return;
var r = canvas.getBoundingClientRect(),
x = e.clientX - r.left,
y = e.clientY - r.top;
// you needed a bit more code here:
ctx.fillStyle = "rgba(255, 255, 255, 0.5)"
ctx.save();
ctx.globalCompositeOperation = 'destination-out';
ctx.beginPath();
ctx.arc(x, y, 25, 0, 2 * Math.PI, false);
ctx.fill();
ctx.restore();
};
.main-content {
position: relative;
width: 300px;
height: 300px;
}
.main-text {
position: absolute;
right: 0;
left: 0;
text-align: center;
z-index: 8;
font-size: 35px;
}
#canvas {
/*background-color: green;*/
position: absolute;
z-index: 9;
}
<div class="main-content">
<p class="main-text">You Won!!!</p>
<canvas id="canvas" width=300 height=300 style="border: 1px solid green;"></canvas>
<div id="spots"></div>
</div>
About canvas global compositions: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
You are halfway there, but there are a couple of things to change.
What you're trying to do is to change the appearance of a css property through the canvas, which does not work like that. You also can not alter the transparency of the canvas, however there are solutions to your case, and it is very simple.
You need to apply a background color to your canvas, then remove the pixels by using the exact same drawing function you have, then you set an extra property called globalCompositeOperation, which is basically the "BlendMode" of photoshop.
So here's your updated code:
var cont = document.getElementById("spots"), // UI elements
canvas = document.getElementById("canvas"),
alpha = document.getElementById("alpha"),
modes = document.getElementById("modes"),
ctx = canvas.getContext("2d"),
isDown = false, // defaults
color = "red";
// set up color palette using a custom "Spot" object
// This will use a callback function when it is clicked, to
// change current color
function Spot(color, cont, callback) {
var div = document.createElement("div");
div.style.cssText = "width:50px;height:50px;border:1px solid #000;margin:0 1px 1px 0;background:" + color;
div.onclick = function() {callback(color)};
cont.appendChild(div);
}
// add some spot colors to our palette container
//new Spot(color, cont, setColor);
// this will set current fill style based on spot clicked
function setColor(c) {ctx.fillStyle = c}
// setup defaults
ctx.fillStyle = color;
ctx.globalAlpha = 1;
// draw the background
ctx.fillRect(0, 0, 300, 300);
// add the 'blend mode effect'
ctx.globalCompositeOperation = 'destination-out';
// events
canvas.onmousedown = function() {isDown = true};
window.onmouseup = function() {isDown = false};
window.onmousemove = function(e) {
if (!isDown) return;
var r = canvas.getBoundingClientRect(),
x = e.clientX - r.left,
y = e.clientY - r.top;
ctx.beginPath();
ctx.arc(x, y, 25, 0, 2*Math.PI);
ctx.fill();
};
.main-content{
position: relative;
width: 300px;
height: 300px;
background: blue;
}
.main-text{
position: absolute;
right: 0;
left: 0;
text-align: center;
z-index: 8;
font-size: 35px
}
#canvas{
position: absolute;
z-index: 9;
}
<div class="main-content">
<p class="main-text">You Won!!!</p>
<canvas id="canvas" width=300 height=300 style="border: 1px solid green;"></canvas>
<div id="spots"></div>
</div>
Apart from that tiny change on the JS, I also changed the order of z-index on the CSS. Good luck

Draw dashed and dotted rectangles on canvas in the same way css border works: draw 4 same edges

My use-case is to mimic css border rendering. Is it possible, using the CanvasRenderingContext2D::rect method with CanvasRenderingContext2D::setLineDash to simulate same border drawing as css renderer does, like border: 5px dashed red. Consider this example:
let canvas = document.querySelector('canvas');
let ctx = canvas.getContext('2d');
ctx.lineWidth = 5
ctx.strokeStyle = 'red'
ctx.lineCap = 'square'
ctx.setLineDash([10, 10]);
ctx.beginPath();
ctx.moveTo(2.5,2.5);
ctx.rect(2.5, 2.5, 195, 65);
ctx.stroke();
div {
border: 5px dashed red;
width: 200px;
height: 70px;
box-sizing: border-box;
margin-bottom: 5px;
}
canvas {
display: block;
width: 200px;
height: 70px;
}
<div></div>
<canvas width=200 height=70></canvas>
You may notice the problem is on edges.
I was trying to modify the gaps and dash sizes, but it seems impossible to get the same behaviour as in the css example: the lines on edges are bigger then the lines on the sides. As a workaround I can imagine to draw every side with a line, but I would like to use the rect method to draw in one stroke.
Thank you in advance.
CSS border-style: dashed algorithm is not tied by specs, so it will be impossible to render exactly the same in the canvas API.
Then, you've got to know that even CSS renders it line by line: border is a shorthand for all the border-top-XXX, border-right-XXX, border-bottom-XXX, border-left-XXX.
And that's why it behaves like that : each border has its line-dash set independently of the others.
Anyway, if you want to do it with the canvas API, the easiest solution is to do the same, using four lines, and setting their line-dash separately.
Here is a rough attempt at normalizing the dashes in order to get them always start and end at edges:
var ctx = c.getContext('2d');
ctx.lineCap = 'square';
// returns a normalized dashArray per segment
// This in no way does the same as any browser's implementation,
// this is just a lazy way to always get dashes start and end at edges
function getLineDash(x1, y1, x2, y2) {
var length = Math.hypot((x2 - x1), (y2 - y1));
var dash_length = length / 8;
var nb_of_dashes = length / dash_length;
var dash_gap = (dash_length * 0.66);
dash_length -= dash_gap * 0.33;
return [dash_length, dash_gap];
}
function draw() {
ctx.lineWidth = lineWidth_.value;
ctx.clearRect(0, 0, c.width, c.height);
var points = [
[x1_.value, y1_.value],
[x2_.value, y2_.value],
[x3_.value, y3_.value],
[x4_.value, y4_.value]
];
points.forEach(function(pt, i) {
var next = points[(i + 1) % points.length];
ctx.beginPath();
ctx.moveTo(pt[0], pt[1]);
ctx.lineTo(next[0], next[1]);
ctx.setLineDash(getLineDash(pt[0], pt[1], next[0], next[1]));
ctx.stroke();
});
}
draw();
document.oninput = function(e) {
if (e.target.parentNode.parentNode === inputs_) {
draw();
}
}
label {
display: inline-block;
}
input {
max-width: 50px;
}
<div id="inputs_">
<label>x1<input type="number" id="x1_" value="10"></label>
<label>y1<input type="number" id="y1_" value="25"></label>
<label>x2<input type="number" id="x2_" value="350"></label>
<label>y2<input type="number" id="y2_" value="25"></label>
<label>x3<input type="number" id="x3_" value="350"></label>
<label>y3<input type="number" id="y3_" value="225"></label>
<label>x4<input type="number" id="x4_" value="10"></label>
<label>y4<input type="number" id="y4_" value="225"></label>
<label>lineWidth<input type="number" id="lineWidth_" value="3"></label>
</div>
<canvas id="c" width="400" height="400"></canvas>
So now, if you only want to use XXXRect, you can also create a single huge dash-array containing all of the dashes...
var ctx = c.getContext('2d');
ctx.lineCap = 'square';
function getRectDashes(width, height) {
var w_array = getLineDashes(width, 0, 0, 0);
var h_array = getLineDashes(0, height, 0, 0);
dashArray = [].concat.apply([], [w_array, 0, h_array, 0, w_array, 0, h_array]);
return dashArray;
}
// same as previous snippet except that it does return all the segment's dashes
function getLineDashes(x1, y1, x2, y2) {
var length = Math.hypot((x2 - x1), (y2 - y1));
var dash_length = length / 8;
var nb_of_dashes = length / dash_length;
var dash_gap = dash_length * 0.66666;
dash_length -= dash_gap * 0.3333;
var total_length = 0;
var dasharray = [];
var next;
while (total_length < length) {
next = dasharray.length % 2 ? dash_gap : dash_length;
total_length += next;
dasharray.push(next);
}
return dasharray;
}
function draw() {
ctx.clearRect(0, 0, c.width, c.height);
ctx.lineWidth = lineWidth_.value;
var w = width_.value,
h = height_.value;
ctx.setLineDash(getRectDashes(w, h));
ctx.strokeRect(20, 20, w, h);
}
draw();
document.oninput = function(e) {
if (e.target.parentNode.parentNode === inputs_)
draw();
};
label {
display: inline-block;
}
input {
max-width: 50px;
}
<div id="inputs_">
<label>width<input type="number" id="width_" value="200"></label>
<label>height<input type="number" id="height_" value="225"></label>
<label>lineWidth<input type="number" id="lineWidth_" value="3"></label>
</div>
<canvas id="c" width="400" height="400"></canvas>

Make canvas fill the whole page

How can I make canvas be 100% in width and height of the page?
Well I have it working here: Are Google's Bouncing Balls HTML5? by using the following CSS:
* { margin: 0; padding: 0;}
body, html { height:100%; }
#c {
position:absolute;
width:100%;
height:100%;
}
Where #c is the id of the canvas element.
you can use these codes without jquery
var dimension = [document.documentElement.clientWidth, document.documentElement.clientHeight];
var c = document.getElementById("canvas");
c.width = dimension[0];
c.height = dimension[1];
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
maybe that easy?
This has something to do with <canvas> tag.
when create fullscreen canvas, <canvas> will cause scrollbar if not set to display:block.
detail: http://browser.colla.me/show/canvas_cannot_have_exact_size_to_fullscreen
You can programatically set the canvas width + height:
// Using jQuery to get window width + height.
canvasObject.width = $(window).width();
canvasObject.height = $(window).height();
I've tested this and it as long as you redraw what's on the canvas after you've resized it won't change the scaling.
on my observations this runs effectively, and gives a blue shade
var c = document.getElementById('can');
var ctx = canvas.getContext('2d');
ctx.rect(0, 0, canvas.width, canvas.height);
// add linear gradient
var g = ctx.createLinearGradient(0, 0, c.width, c.height);
// light blue color
g.addColorStop(0, '#8ED6FF');
// dark blue color
g.addColorStop(1, '#004CB3');
context.fillStyle = g;
context.fill();
<script>
var c = document.getElementById('can');
var ctx = canvas.getContext('2d');
ctx.rect(0, 0, canvas.width, canvas.height);
// add linear gradient
var g = ctx.createLinearGradient(0, 0, c.width, c.height);
// light blue color
g.addColorStop(0, '#8ED6FF');
// dark blue color
g.addColorStop(1, '#004CB3');
context.fillStyle = g;
context.fill();
</scrip
html,body
{
height:98.0%;
width:99.5%;
}
canvas
{
display:block;
width:100%;
height:100%;
}
<html>
<body>
<canvas id="can"></canvas>
</body>
</html>
Does this work for you?
<html>
<body style="height: 100%; margin: 0;">
<canvas style="width: 100%; height: 100%;"></canvas>
</body>
</html>
from Force canvas element in html to take up the entire window?

Categories