If I try to draw text to my canvas at the onload event, the text shows up blurry. I draw to the same canvas later via a button click from another function and it's fine. But if I call this function from the button, it's still blurry. Can anybody see something wrong in this code?
window.onload = initCanvasRender;
function initCanvasRender() {
var c = document.getElementById("canvas");
var ctx = c.getContext('2d');
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = 'black';
ctx.font = '20px Times New Roman';
ctx.fillText('hello...', c.width/2, c.height/2);
}
There may be some problem with the
ctx.fillText('hello...', c.width/2, c.height/2);
Because if you for example set width of the canvas with css then c.width and c.height will be the default size for the canvas which is, 300x150 and not the size defined in css. Try to set two variables for the width and height that are global for your application. E.g
var canvasWidth = 400;
var canvasHeight = 200;
c.width = canvasWidth;
c.height = canvasHeight;
/* ... */
and then later in your code you can use canvasWidth and canvasWeight:
ctx.fillText('hello...', canvasWidth/2, canvasHeight/2);
Take a look at this test: http://jsfiddle.net/EsQfb/7/ it's important to use use the canvas.width and not canvas.style.width in your case.
Take a look at this for more information about this: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#attr-canvas-width
Related
I have an issue with the painting context.stokeText that style contains an alpha. A big value of line width makes some effect of intersected strokes, as a result, the color is darker.
How I can avoid this?
ctx.strokeStyle ="rgba(0,0,0,0.3)";
ctx.lineWidth = 15;
ctx.lineJoin="round";
ctx.strokeText(text, x, y);
Image
That's a bit of an inconsistency in the specs since usually overlapping sub-pathes are painted only once.
However strokeText() does create one shape per glyph, and thus this method will indeed paint each glyphs on their own, creating this visible overlapping.
To overcome this, you'll to be a bit creative:
first draw your text fully opaque,
then redraw the produced pixels with the desired alpha level (many ways to do so).
draw that on your scene (or draw the background behind).
Here are a few ways (there are many others):
Probably the easiest, but which costs more memory: use a second disconnected canvas:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
// create a new canvas just for the text
const canvas2 = canvas.cloneNode();
const ctx2 = canvas2.getContext("2d");
ctx2.font = "60px sans-serif";
const text = "MY TEXT";
const x = canvas.width - ctx2.measureText(text).width - 20;
const y = canvas.height - 20;
// draw it fully opaque
ctx2.lineWidth = 15;
ctx2.lineJoin="round";
ctx2.strokeText(text, x, y);
// draw the background on the visible canvas
ctx.fillStyle = "#ffe97f";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// now draw our text canvas onto the visible one
// with the desired opacity
ctx.globalAlpha = 0.3;
ctx.drawImage(canvas2, 0, 0);
<canvas width="465" height="234"></canvas>
More memory friendly, but which requires you to rewrite your drawing logic in a different direction, use compositing:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.font = "60px sans-serif";
const text = "MY TEXT";
const x = canvas.width - ctx.measureText(text).width - 20;
const y = canvas.height - 20;
// first draw the text fully opaque
ctx.lineWidth = 15;
ctx.lineJoin="round";
ctx.strokeText(text, x, y);
// now apply the opacity
ctx.fillStyle ="rgba(0,0,0,0.3)";
ctx.globalCompositeOperation = "source-in";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// and the background
ctx.fillStyle = "#ffe97f";
ctx.globalCompositeOperation = "destination-over";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// if you want to keep drawing "normaly"
ctx.globalCompositeOperation = "source-over";
<canvas width="465" height="234"></canvas>
A mix of both, with different compositing rules:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.font = "60px sans-serif";
const text = "MY TEXT";
const x = canvas.width - ctx.measureText(text).width - 20;
const y = canvas.height - 20;
// first draw the text fully opaque
ctx.lineWidth = 15;
ctx.lineJoin="round";
ctx.strokeText(text, x, y);
// now redraw over itself with the desired opacity
ctx.globalAlpha = 0.3;
ctx.globalCompositeOperation = "copy";
ctx.drawImage(canvas, 0, 0);
ctx.globalAlpha = 1;
// and the background
ctx.fillStyle = "#ffe97f";
ctx.globalCompositeOperation = "destination-over";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// if you want to keep drawing "normaly"
ctx.globalCompositeOperation = "source-over";
<canvas width="465" height="234"></canvas>
How I can take value from a button and insert into a canvas to fill, for example if I press button T the box where is canvas will fill pattern with letter T entire box, I have an example but I don't want to copy the code I want to understand how it was made because I want to make something more advanced but I need to understand this first, here is the link example of feature.
For this I'm using 3 canvases. Two canvases are hidden but you don't need to attach them to the DOM. The logic is like this:
You have a first canvas (canvas1) where you draw the text from the <textarea>
You create a pattern using the canvas1: ctx2.fillStyle = ctx2.createPattern(canvas1,"repeat-x"); and you use it to fill a second canvas canvas2. This second canvas is twice as wide as the main canvas.
You use the canvas2 as a background image for the main canvas Here you may use a loop. I didn't.
let fontSize = 50;
const canvas = document.querySelector("canvas");
const canvas1 = document.createElement("canvas");
const canvas2 = document.createElement("canvas");
test.appendChild(canvas1);// you don't need to attach this to the DOM
test.appendChild(canvas2);// you don't need to attach this to the DOM
canvas.width = 500;
canvas.height = fontSize * 4;
canvas1.height = fontSize;
canvas2.height = fontSize;
canvas2.width = 2*canvas.width;
let ctx = canvas.getContext("2d");
let ctx1 = canvas1.getContext("2d");
let ctx2 = canvas2.getContext("2d");
ctx1.font = fontSize+"px 'Lucida Console', monospace";
theText.addEventListener("input",()=>{
ctx2.clearRect(0,0, canvas2.width, canvas2.height);
ctx.clearRect(0,0, canvas.width, canvas.height);
let _text = theText.value.toUpperCase();
let textLength = ctx1.measureText(_text).width;
canvas1.width = textLength || 1;// to avoid width 0 of the canvas
ctx1.font = fontSize+"px 'Lucida Console', monospace";
ctx1.fillStyle = "blue";
ctx1.textBaseline="middle";
ctx1.fillText(_text, 0, canvas1.height/2);
ctx2.fillStyle = ctx2.createPattern(canvas1,"repeat-x");
ctx2.fillRect(0,0, canvas2.width, canvas2.height);
ctx.drawImage( canvas2, 0, 0 );
ctx.drawImage( canvas2, -canvas2.width/2, fontSize );
ctx.drawImage( canvas2, canvas2.width/2, fontSize );
ctx.drawImage( canvas2, 0, 2*fontSize );
ctx.drawImage( canvas2, -canvas2.width/2, 3*fontSize );
ctx.drawImage( canvas2, canvas2.width/2, 3*fontSize );
})
canvas{border:1px solid;}
#test{display:none}
<canvas></canvas>
<textarea id="theText"></textarea>
<div id="test"></div>
In order to understand better what happens please delete #test{display:none} from the css. I hope this helps.
How can I achieve a blur behind a transparent box (fillStyle = 'rgba(255, 255, 255, 0.2)') in JavaScript canvas? Here's what I've got so far:
var canvas = document.getElementById('draw');
var c = canvas.getContext('2d');
function main() {
c.fillStyle = '#222';
c.fillRect(0, 0, canvas.width, canvas.height);
c.fillStyle = '#000';
c.fillRect(32, 32, 64, 64);
c.fillStyle = 'rgba(255, 255, 255, 0.2)';
c.filter = 'blur(5px)';
c.fillRect(16, 16, 128, 24);
}
But what happens, is instead of blurring the background behind the rectangle, is the rectangle itself is blurred, kind of obviously.
In the final script, I will probably use paths instead of rects.
Context2D filters will be applied only on your new drawings, so to also blur the background, you would actually have to redraw the part of the background you want to be blurred.
Fortunately, canvas can drawImage itself.
var blurredRect = {
x: 80,
y: 80,
height: 200,
width: 200,
spread: 10
};
var ctx = canvas.getContext('2d');
var img = new Image();
img.onload = draw;
img.src = 'https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg';
function draw() {
canvas.width = img.width / 2;
canvas.height = img.height / 2;
// first pass draw everything
ctx.drawImage(img, 0,0, canvas.width, canvas.height);
// next drawings will be blurred
ctx.filter = 'blur('+ blurredRect.spread +'px)';
// draw the canvas over itself, cropping to our required rect
ctx.drawImage(canvas,
blurredRect.x, blurredRect.y, blurredRect.width, blurredRect.height,
blurredRect.x, blurredRect.y, blurredRect.width, blurredRect.height
);
// draw the coloring (white-ish) layer, without blur
ctx.filter = 'none'; // remove filter
ctx.fillStyle = 'rgba(255,255,255,0.2)';
ctx.fillRect(blurredRect.x, blurredRect.y, blurredRect.width, blurredRect.height);
}
<canvas id="canvas"></canvas>
But, canvas blur filter is a bit different than CSS one in that it will make the spreading stay inside the drawn area. This means that in our case, we have a 5px border around our rectangle that is less blurred than the center.
To workaround, we can take the whole thing in a different order and play with globalCompositeOperation property*:
var blurredRect = {
x: 80,
y: 80,
height: 200,
width: 200,
spread: 10
};
var ctx = canvas.getContext('2d');
var img = new Image();
img.onload = draw;
img.src = 'https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg';
function draw() {
var spread = blurredRect.spread,
ratio = 0.5,
// make our blurred rect spreads
x = blurredRect.x - spread,
y = blurredRect.y - spread,
w = blurredRect.width + (spread * 2),
h = blurredRect.height + (spread * 2);
canvas.width = img.width * ratio;
canvas.height = img.height * ratio;
// this time we will first draw the blurred rect
ctx.filter = 'blur('+ spread +'px)';
// this time we draw from the img directly
ctx.drawImage(img,
x / ratio, y / ratio, w / ratio, h / ratio,
x, y, w, h
);
// now we will want to crop the resulting blurred image to the required one, so we get a clear-cut
ctx.filter = 'none'; // remove filter
// with this mode, previous drawings will be kept where new drawings are made
ctx.globalCompositeOperation = 'destination-in';
ctx.fillStyle = '#000'; // make it opaque
ctx.rect(blurredRect.x, blurredRect.y, blurredRect.width, blurredRect.height);
ctx.fill(); // clear-cut done
// reuse our rect to make the white-ish overlay
ctx.fillStyle = 'rgba(255,255,255,0.2)';
// reset gCO to its default
ctx.globalCompositeOperation = 'source-over';
ctx.fill();
// now we will draw behind the our blurred rect
ctx.globalCompositeOperation = 'destination-over';
ctx.drawImage(img, 0,0, canvas.width, canvas.height);
// reset to defaults
ctx.globalCompositeOperation = 'source-over';
}
<canvas id="canvas"></canvas>
But this approach requires that we keep access to the whole background as a drawable thing, in the example above that was just an image, but in real life, this might mean you'd have to do this operation on a second offscreen canvas.
var blurredRect = {
x: 80,
y: 80,
height: 200,
width: 200,
spread: 2
};
var ctx = canvas.getContext('2d');
// create an off-screen canvas
var bCanvas = canvas.cloneNode();
var bCtx = bCanvas.getContext('2d');
var img = new Image();
img.onload = draw;
img.src = 'https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg';
function draw() {
var spread = blurredRect.spread;
canvas.width = bCanvas.width = img.width / 2;
canvas.height = bCanvas.height = img.height / 2;
// now we have a composed background
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
ctx.font = '40px Impact';
ctx.fillStyle = 'white';
ctx.fillText('..SO BLUR ME..', 120, 282);
// make our clear-cut on the offscreen canvas
bCtx.filter = 'blur(' + spread +'px)';
bCtx.drawImage(canvas,
blurredRect.x - spread, blurredRect.y - spread, blurredRect.width + spread * 2, blurredRect.height + spread * 2,
blurredRect.x - spread, blurredRect.y - spread, blurredRect.width + spread * 2, blurredRect.height + spread * 2
);
// clear-cut
bCtx.filter = 'none';
bCtx.globalCompositeOperation = 'destination-in';
bCtx.beginPath();
bCtx.rect(blurredRect.x, blurredRect.y, blurredRect.width, blurredRect.height);
bCtx.fillStyle = '#000';
bCtx.fill();
// white-ish layer
bCtx.globalCompositeOperation = 'source-over';
bCtx.fillStyle = 'rgba(255,255,255,0.2)';
bCtx.fillRect(blurredRect.x, blurredRect.y, blurredRect.width, blurredRect.height);
// now just redraw on the visible canvas
ctx.drawImage(bCanvas, 0,0);
}
<canvas id="canvas"></canvas>
*One may say that instead of an offscreen canvas and gCO we could have used ctx.clip(), but since you said it might a more complex Path than a rect, I will not advise to do so. Indeed, while it would require less code, and maybe use less memory, clipping is just bad with antialiasing, and since you are doing blurring, that will just look plain ugly.
I am working on a PWA, which will be used to conduct surveys, so what I'm doing is,
I'm capturing a snapshot from a video(within the app) and saving it in a canvas, which works fine.
Now I need to add date, time and geo-coordinates on it.
My Javascript code
var video = document.querySelector('video');
var takenPhotosDiv = document.getElementById( "taken-photos" );
var button = document.querySelector('button');
button.onclick = function() {
drawCanvas();
};
var drawCanvas = function(){
var canvas = window.canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
canvas.getContext('2d').fillText( "30-08-2017" + " " + "15:25" + " " + "(79.85858454, 17.56852655)", 50, 150 );
takenPhotosDiv.appendChild( canvas );
}
The above code works fine and it does get close to what's being expected, here's the sample output of the above code
The final text-format should look like this (the text bar should be at the bottom of the image and not in the middle and with much bigger font)
PS: I don't just have to display this in the above mentioned format, even need to save and push it on Firebase later.
Edit:
var addTextToCanvas = function( canvas ){
canvas.lineWidth = 2;
canvas.fillStyle = "blue";
canvas.font = "bold 20px sans-serif";
canvas.textBaseline = "bottom";
canvas.fillText( "30-08-2017" + " " + "15:25" + " " + "(79.85858454, 17.56852655)", 0, 100 );
return canvas;
};
I tried this, but the font and font size remained the same.
This function is called from drawCanvas(), just before appending it to div, since it didn't work, I simply added called fillText on the canvas there itself
Edit 2:
Make sure that the methods and properties are set on the context not the canvas element itself.
We also need to calculate the actual vertical position of the text. Since it's aligned to the bottom we can use the height of the canvas minus some bottom padding:
var y = canvas.height - 10;
So, for example:
var addTextToCanvas = function( context ) { // pass in 2D context
var y = context.canvas.height - 10;
context.fillStyle = "blue";
context.font = "bold 20px sans-serif";
context.textBaseline = "bottom";
context.fillText( "30-08-2017"+" "+"15:25"+" "+"(79.85858454, 17.56852655)", 10, y );
return context;
};
or if you prefer to pass in the canvas:
var addTextToCanvas = function( canvas ) {
var context = canvas.getContext("2d");
var y = canvas.height - 10;
context.fillStyle = "blue";
context.font = "bold 20px sans-serif";
context.textBaseline = "bottom";
context.fillText( "30-08-2017"+" "+"15:25"+" "+"(79.85858454, 17.56852655)", 10, y );
return context;
};
The lineWidth doesn't do anything here so it can be removed.
I would recommend that you store the context once globally. It's the same context you get each time anyways but there is more overhead requesting it each time it will be used.
Functional example:
var ctx = c.getContext("2d");
var addTextToCanvas = function( context ) {
context.fillStyle = "blue";
context.font = "bold 20px sans-serif";
context.textBaseline = "bottom";
var y = context.canvas.height - 10;
context.fillText( "30-08-2017"+" "+"15:25"+" "+"(79.85858454, 17.56852655)", 10, y );
return context;
};
addTextToCanvas(ctx);
#c {border: 1px solid #999}
<canvas id=c width=600 height=180></canvas>
And finally, to extract as image the call needs to be made on the canvas element not context (can be confusing):
var dataUrl = canvas.toDataURL(); // saves out PNG image
or for JPEG:
var dataUrl = canvas.toDataURL("image/jpeg", 0.75);
Try this
var canvas = window.canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var text = "30-08-2017\n15:25\n(79.85858454, 17.56852655)";
ctx.font = "30px Arial";
ctx.fillStyle = "red";
ctx.textAlign = "center";
ctx.fillText(text, canvas.width/2, canvas.height/2);
When I add
ctx.addEventListener('mousedown', onDown, false);
The canvas drawing (background and shapes) disappear and the page is blank, and then when I remove this event listener from the code they reappear again. Just wondering why this is happening? Thanks in advance
<script>
var ctx, W, H;
var x = 10;
window.onload = function() {
var canvas = document.getElementById("canvas");
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
ctx = canvas.getContext("2d");
ctx.addEventListener('mousedown', onDown, false); //When this is here, canvas drawing disappears, when it's not here canvas drawing reappears again
setInterval(draw, 1);
function draw() {
ctx.globalCompositeOperation = "source-over";
ctx.fillStyle = "#E6E6FF";
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = "black";
ctx.fillRect(x,20,10,10);
ctx.font = "30px Arial";
ctx.fillText("Hello World",10,80);
ctx.fill();
}
}
function onDown(event) {
//where x is found
cx = event.pageX
cy = event.pageY
alert("X,Y ="+cx+','+cy);
}
You can't add an event listener to the canvas's context. You'll need to add it to the canvas itself.
Instead of:
ctx.addEventListener('mousedown', onDown, false);
… do this:
canvas.addEventListener('mousedown', onDown, false);
jsBin demo
use:
ctx.canvas.addEventListener
or:
canvas.addEventListener
cause context is just an Object in which the HTMLElementCanvas lives in.
To spot such errors your-self, the easiest way is to debug your code using Developer Tools, opening the console tab and reading the errors you're shown: