Blurry text still in canvas - javascript

I am using Konva and creating a html canvas object and setting it as my shape's image.
I am drawing the width and height of this image shape inside its canvas (across the top and down the left) and this image is also scalable so I am trying to solve blurry text problems which arise when my shape (canvas) is rotated or scaled using Konva transformer.
By reading up on this problem I have come up with the following code which has nearly fixed the problem for me but I am still not getting crisp text when the shape is resized out of its initial ratio - so if I drag to resize only width or height then my text starts getting blurry again but if I scale by dragging the corner of Transformer or resize some width and then some height my Font is crisp.
Can anyone explain why this is still happening please?
function fillRectangle(isHover)
{
if(lastItem)//last touched or last added shape
{
var lastWidth = ""+parseInt(lastItem.getClientRect().width);
var lastHeight = ""+parseInt(lastItem.getClientRect().height);
if(parseInt(lastItem.getClientRect().width) != lastItem.originalWidth)
{
var oldWidth = lastItem.getClientRect().width;
var oldHeight = lastItem.getClientRect().height;
lastItem.actual_canvas.width = oldWidth ;//* ratio;
lastItem.actual_canvas.height = oldHeight ;//* ratio;
}
var ctx = lastItem.canvas; //canvas contains the ctx
ctx.clearRect(0, 0, lastItem.actual_canvas.width, lastItem.actual_canvas.height);
ctx.fillStyle="#fff";
if(lastItem.isColliding)
ctx.fillStyle = 'red';
else if(isHover)
ctx.fillStyle = 'black';
ctx.fillRect(0,0,lastItem.originalWidth * (lastItem.actual_canvas.width / lastItem.originalWidth), lastItem.originalHeight * (lastItem.actual_canvas.height / lastItem.originalHeight));
ctx.stroke();
ctx.save();
if( isHover || lastItem.isColliding)
ctx.fillStyle = 'red';
else
ctx.fillStyle = 'black';
var posX = (lastItem.actual_canvas.width/2);
var posY = (lastItem.actual_canvas.height/2);
if(lastItem.rotation() == 90 || lastItem.rotation() == 270)
{
var fontSize = 12 * (lastItem.actual_canvas.width / lastItem.originalWidth);
ctx.font = fontSize+"px sans-serif";
ctx.textBaseline = 'top';
ctx.textAlign = 'center';
ctx.fillText(lastWidth,posX, 2);
ctx.translate( 0, 0 );
ctx.save();
ctx.rotate(Math.PI/2 );
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
ctx.fillText(lastHeight,posY,-(lastItem.actual_canvas.width/15));
ctx.save();
ctx.restore();
}
else//roation must be 0 or 180
{
var fontSize = 12 * (lastItem.actual_canvas.width / lastItem.originalWidth);
ctx.font = fontSize+"px sans-serif";
ctx.textBaseline = 'top';
ctx.textAlign = 'center';
ctx.fillText(lastWidth,posX, 2);
ctx.translate( 0, 0 );
ctx.save();
ctx.rotate(Math.PI/2 );
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
ctx.fillText(lastHeight,posY,-(lastItem.actual_canvas.width/15));
ctx.save();
ctx.restore();
}
layer.draw();
}
else
console.log("lastitem is null");
}
image of blurry text problem

The canvas will automatically blur graphics that are given coordinates that are decimals.
Try rounding/casting them to the nearest integer.
ctx.fillText("Text",xPos | 0,yPos | 0);
ctx.fillText("Text",parseInt(xPos),parseInt(yPos));

Related

Filling in two colors while using the rect() method

I was trying to make two different shapes that are different colors but it isn't working. Both of the shapes are the same colors. Please help!(Please note that I am not the best coder in the world)
I've looked for other examples on this website, but all of them use the lineTo() method and I would like to use the rect() method just to make things easier.
//make canvas and set it up
var canvas = document.createElement('canvas');
document.body.appendChild(canvas);
var ctx = canvas.getContext('2d');
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
canvas.style.position = 'absolute';
canvas.style.left = '0px';
canvas.style.top = '0px';
canvas.style.backgroundColor = '#D0C6C6';
var cH = canvas.height;
var cW = canvas.width;
//draw paddles
//variables
var paddleLength = 120;
var redPaddleY = window.innerHeight / 2;
var bluePaddleY = window.innerHeight / 2;
var paddleWidth = 20;
//drawing starts
function drawPaddles() {
//RED PADDLE
var redPaddle = function(color) {
ctx.fillStyle = color;
ctx.clearRect(0, 0, cW, cH);
ctx.rect(cH / 12, redPaddleY - paddleLength / 2, paddleWidth, paddleLength);
ctx.fill();
};
//BLUE PADDLE
var bluePaddle = function(color) {
ctx.fillStyle = color;
ctx.clearRect(0, 0, cW, cH);
ctx.rect(cH / 12 * 14, bluePaddleY - paddleLength / 2, paddleWidth, paddleLength);
ctx.fill();
};
redPaddle('red');
bluePaddle('blue');
};
var interval = setInterval(drawPaddles, 25);
Whenever you add a shape to the canvas it becomes part of the current path. The current path remains open until you tell the canvas to start a new one with beginPath(). This means that when you add your second rect() it is combined with the first and filled with the same colour.
The simplest fix would be to use the fillRect() function instead of rect which begins, closes and fills a path in one call.
var redPaddle = function(color) {
ctx.fillStyle = color;
ctx.fillRect(cH / 12, redPaddleY - paddleLength / 2, paddleWidth, paddleLength);
};
If you still want to use rect() you should tell the canvas to begin a new path for each paddle.
var redPaddle = function(color) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.rect(cH / 12, redPaddleY - paddleLength / 2, paddleWidth, paddleLength);
ctx.fill();
};
I would also suggest moving the clearRect() outside of the drawing functions too. Clear once per frame and draw both paddles.
...
ctx.clearRect(0, 0, cW, cH);
redPaddle();
bluePaddle();
...
You should also investigate requestAnimationFrame() to do your animation loop as it provides many performance improvements over intervals.

Inset-shadow on HTML5 canvas image

I've seen this question before but the answers given are for canvas images that have been drawn on via path however, i'm drawing an image.
Is it possible to create an inset-shadow?
context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
context.shadowBlur = 10;
context.shadowColor = 'rgba(30,30,30, 0.4)';
var imgOne = new Image();
imgOne.onload = function() {
context.drawImage(imgOne, 0, 0);
};
imgOne.src = "./public/circle.png";
So I draw the circle picture on. I've now at the moment got a slight shadow on the outside of the circle, how can I get this inset instead of offset?
Composition chain
Use a series of composite + draw operation to obtain inset shadow.
Note: the solution require exclusive access to the canvas element when created so either do this on an off-screen canvas and draw back to main, or if possible, plan secondary graphics to be drawn after this has been generated.
The needed steps:
Draw in original image
Invert alpha channel filling the canvas with a solid using xor composition
Define shadow and draw itself back in
Deactivate shadow and draw in original image (destination-atop)
var ctx = c.getContext("2d"), img = new Image;
img.onload = function() {
// draw in image to main canvas
ctx.drawImage(this, 0, 0);
// invert alpha channel
ctx.globalCompositeOperation = "xor";
ctx.fillRect(0, 0, c.width, c.height);
// draw itself again using drop-shadow filter
ctx.shadowBlur = 7*2; // use double of what is in CSS filter (Chrome x4)
ctx.shadowOffsetX = ctx.shadowOffsetY = 5;
ctx.shadowColor = "#000";
ctx.drawImage(c, 0, 0);
// draw original image with background mixed on top
ctx.globalCompositeOperation = "destination-atop";
ctx.shadowColor = "transparent"; // remove shadow !
ctx.drawImage(this, 0, 0);
}
img.src = "http://i.imgur.com/Qrfga2b.png";
<canvas id=c height=300></canvas>
Canvas will shadow where an image changes from opaque to transparent so, as K3N shows in his correct answer, you can turn the image inside out (opaque becomes transparent & visa-versa) so the shadows are drawn inside the circle.
If you know your circle's centerpoint and radius, you can use a stroked-path to create an inset circle shadow. Here's an example:
var canvas=document.getElementById("canvas");
var context=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
context.beginPath();
context.arc(cw/2,ch/2,75,0,Math.PI*2);
context.fillStyle='lightcyan';
context.fill();
context.globalCompositeOperation='source-atop';
context.shadowOffsetX = 500;
context.shadowOffsetY = 0;
context.shadowBlur = 15;
context.shadowColor = 'rgba(30,30,30,1)';
context.beginPath();
context.arc(cw/2-500,ch/2,75,0,Math.PI*2);
context.stroke();
context.stroke();
context.stroke();
context.globalCompositeOperation='source-over';
<canvas id="canvas" width=300 height=300></canvas>
If your path is irregular or hard to define mathematically, you can also use edge-path detection algorithms. One common edge-path algorithm is Marching Squares. Stackoverflow's K3N has coded a nice Marching Squares algorithm.
Inspired by markE's answer , I made my own version based on a png instead of vector-graphics.
Additionnaly, I made possible to choose the true alpha of the shadow (because the default shadow strength is a way too soft in my opinion)
var img = document.getElementById("myImage");
img.onload = function(){
createInnerShadow(this,5,1);
}
function createInnerShadow(img,distance,alpha){
//the size of the shadow depends on the size of the target,
//then I will create extra "walls" around the picture to be sure
//tbat the shadow will be correctly filled (with the same intensity everywhere)
//(it's not obvious with this image, but it is when there is no space at all between the image and its border)
var offset = 50 + distance;
var hole = document.createElement("canvas");
var holeContext = hole.getContext("2d");
hole.width = img.width + offset*2;
hole.height = img.height + offset*2;
//first, I draw a big black rect
holeContext.fillStyle = "#000000";
holeContext.fillRect(0,0,hole.width,hole.height);
//then I use the image to make an hole in it
holeContext.globalCompositeOperation = "destination-out";
holeContext.drawImage(img,offset,offset);
//I create a new canvas that will contains the shadow of the hole only
var shadow = document.createElement("canvas");
var shadowContext = shadow.getContext("2d");
shadow.width = img.width;
shadow.height = img.height;
shadowContext.filter = "drop-shadow(0px 0px "+distance+"px #000000 ) ";
shadowContext.drawImage(hole,-offset,-offset);
shadowContext.globalCompositeOperation = "destination-out";
shadowContext.drawImage(hole,-offset,-offset);
//now, because the default-shadow filter is really to soft, I normalize the shadow
//then I will be sure that the alpha-gradient of the shadow will start at "alpha" and end at 0
normalizeAlphaShadow(shadow,alpha);
//Finally, I create another canvas that will contain the image and the shadow over it
var result = document.createElement("canvas");
result.width = img.width;
result.height = img.height;
var context = result.getContext("2d");
context.drawImage(img,0,0)
context.drawImage(shadow,0,0);
//and that's it !
document.body.appendChild(result);
}
function normalizeAlphaShadow(canvas,alpha){
var imageData = canvas.getContext("2d").getImageData(0,0,canvas.width,canvas.height);
var pixelData = imageData.data;
var i,len = pixelData.length;
var max = 0;
for(i=3;i<len;i+=4) if(pixelData[i]>max) max = pixelData[i];
max = (255/max) * alpha;
for(i=3;i<len;i+=4) pixelData[i] *= max;
canvas.getContext("2d").putImageData(imageData,0,0)
}
<html>
<body>
<img id="myImage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAACWCAYAAAB92c4YAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MkFENzg2NTc4MDg5MTFFOEI1OTdBNEZCMEY2OTg3OTAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MkFENzg2NTg4MDg5MTFFOEI1OTdBNEZCMEY2OTg3OTAiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyQUQ3ODY1NTgwODkxMUU4QjU5N0E0RkIwRjY5ODc5MCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyQUQ3ODY1NjgwODkxMUU4QjU5N0E0RkIwRjY5ODc5MCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pk/K8voAADFpSURBVHja5H0HmFXV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" />
</body>
</html>
the jsfiddle is here : https://jsfiddle.net/jrekw5og/141/
Inspired by K3N's answer, I've created Inset.js for this exact situation!
Inset.js
Only requires setting ctx.shadowInset = true;
For example: http://codepen.io/patlillis/pen/ryoWey
var ctx = canvas.getContext('2d');
var img = new Image;
img.onload = function() {
ctx.shadowInset = true;
ctx.shadowBlur = 25;
ctx.shadowColor = "#000";
ctx.drawImage(this, 0, 0);
}
img.src = "http://i.imgur.com/Qrfga2b.png";
const width = 100 * devicePixelRatio;
const height = 100 * devicePixelRatio;
// original canvas
const c = document.getElementById('canvas');
c.width = 300 * devicePixelRatio;
c.height = 300 * devicePixelRatio;
c.style.width = '300px';
c.style.height = '300px';
const cctx = c.getContext('2d');
cctx.fillStyle = 'rgb(20,205,75)';
cctx.arc(150 * devicePixelRatio, 150 * devicePixelRatio, 50 * devicePixelRatio, 0, Math.PI * 2);
cctx.fill();
// temporary canvas
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
canvas.style.width = `${width / devicePixelRatio}px`;
canvas.style.height = `${height / devicePixelRatio}px`;
document.body.appendChild(canvas);
var ctx = canvas.getContext('2d');
// original object on temporary canvas
ctx.arc(50 * devicePixelRatio, 50 * devicePixelRatio, 50 * devicePixelRatio, 0, Math.PI * 2);
ctx.fill();
// shadow cutting
ctx.globalCompositeOperation = 'xor';
ctx.arc(50 * devicePixelRatio, 50 * devicePixelRatio, 50 * devicePixelRatio, 0, Math.PI * 2);
ctx.fill();
// shadow props
ctx.shadowBlur = 50;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = -25;
ctx.shadowColor = '#000';
ctx.arc(50 * devicePixelRatio, 50 * devicePixelRatio, 50 * devicePixelRatio, 0, Math.PI * 2);
ctx.fill();
// shadow color
ctx.globalCompositeOperation = 'source-in';
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// object cutting
ctx.globalCompositeOperation = 'destination-in';
ctx.arc(50 * devicePixelRatio, 50 * devicePixelRatio, 50 * devicePixelRatio, 0, Math.PI * 2);
ctx.fill();
// shadow opacity
cctx.globalAlpha = .4
// inserting shadow into original canvas
cctx.drawImage(canvas, 200, 200);
Colored shadow /w opacity

Draw a text underneath a circle

I'm drawing a circle using html5 canvas and js.
var canvas = document.getElementById(key);
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
var radius = 35;
context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
context.fillStyle = 'red';
context.fill();
context.stroke();
I also want to draw a text right underneath it. I tried this:
context.closePath();
context.fill();
context.fillStyle = "black";
context.font = "bold 11";
context.textBaseline = "top";
context.fillText('Hello', 100-radius/4 ,100-radius/2);
But it was drawn in the right-bottom corner for some reason, whereas I wanted exactly under the circle.
Set textAlign to center, then add radius to y position:
var spacing = 5;
context.textAlign = "center";
context.textBaseline = "top";
context.fillText('Hello', centerX, centerY + radius + spacing);
Your text alignment and coordinates for the text are wrong.
var margin = 10; // Increase the margin add space between Circle & text
context.textBaseline = "top";
context.textAlign = "center";
context.fillText('Hello', centerX , centerY + radius + margin);
JSFiddle with desired outcome

Canvas saving and restoring

I have a canvas that i need to change the height for after every bit of content is added - the only way for me to get a dynamic canvas height. I'm trying to use save and restore so i don't lose all the styles, settings etc.
I can't get save and restore to work. I must be doing something wrong or is this the wrong approach?
function DrawLeftText(text) {
var canvas = document.getElementById('canvasPaper');
if (canvas.getContext) {
var context = canvas.getContext('2d');
context.textAlign = 'left';
context.font = 'normal 20px Monkton';
context.fillText(text, leftPosition, cursor);
context.textAlign = 'start';
context.save();
}
}
function restoreSettings(){
var canvas = document.getElementById('canvasPaper');
if (canvas.getContext) {
var context = canvas.getContext('2d');
context.restore();
}
}
function onDrawReceipt() {
var canvas = document.getElementById('canvasPaper');
if (canvas.getContext) {
var context = canvas.getContext('2d');
context.textBaseline = 'top';
lineSpace = 20;
leftPosition = 0;
// centerPosition = canvas.width / 2;
centerPosition = (canvas.width + 26) / 2;
// rightPosition = canvas.width;
rightPosition = (canvas.width - 0);
// cursor = 0;
cursor = 80;
context.fillRect(25, cursor - 2, 526, 2); cursor += lineSpace;
context.fillRect(25, cursor - 2, 526, 2); cursor += lineSpace;
DrawCenterText(company['name']); cursor += lineSpace;
DrawCenterText(company['address']['address_line_1']); cursor += lineSpace;
DrawCenterText(company['address']['city'].toUpperCase()); cursor += lineSpace;
DrawCenterText(company['address']['postcode']); cursor += lineSpace;
context.clearRect(0, 0, canvas.width, canvas.height += 20);
**//increasing the height and then trying to restore**
restoreSettings();
}
}
////// Additional attempt. I've tried the following too without luck.
////// None of the content appears despite setting the height before adding the content??
var header = function headerSection(){
var height = cursor;
canvas.height += height;
context.textBaseline = 'top';
lineSpace = 20;
leftPosition = 0;
centerPosition = (canvas.width + 26) / 2;
rightPosition = (canvas.width - 0);
console.log(height);
console.log(context);
context.fillRect(25, cursor - 2, 526, 2); cursor += lineSpace;
context.fillRect(25, cursor - 2, 526, 2); cursor += lineSpace;
DrawCenterText(company['name']); cursor += lineSpace;
DrawCenterText(company['address']['address_line_1']); cursor += lineSpace;
DrawCenterText(company['address']['city'].toUpperCase()); cursor += lineSpace;
DrawCenterText(company['address']['postcode']); cursor += lineSpace;
console.log(canvas.height);
return;
}
header()
I would suggest the following approach at the cost of a little more memory (if you're dealing with very large canvases you will of course have to weight in memory concern) -
Use an internal larger canvas representing a max size (or block size1)
Keep a value representing the current height (not actual)
Create a visual canvas in the document at the height stored in the value
For every draw operation, draw to the internal canvas
When done, set the visual canvas height using the current height value, draw internal canvas to visual one
Example 1
This example uses this technique to draw text at random color. As you can see we do not need to redraw the text when we change the visual canvas' size, we just copy everything from the internal canvas which also contains the current styles (font, font size, fill style).
var vcanvas = document.querySelector("canvas"),
vctx = vcanvas.getContext("2d"),
canvas = document.createElement("canvas"),
ctx = canvas.getContext("2d"),
line = 25,
maxHeight = 7 * line,
currentHeight = line,
chars = "abcdefghijklmnowxyzABCD#/&%/(#)=!LMNOPQRSTUVWXYZ".split(""),
x = 0, y = 0, ch;
vcanvas.height = currentHeight;
// internal canvas setup
ctx.font = "20px sans-serif";
ctx.textBaseline = "top";
// draw someting to internal canvas:
(function loop() {
ch = chars[(Math.random() * chars.length)|0];
ctx.fillStyle = "hsl(" + (360*Math.random()) + ",75%,50%)";
ctx.fillText(ch, x, y);
x += ctx.measureText(ch).width;
if (x > canvas.width) {
x = 0; y += line; currentHeight += line;
}
// copy to visual canvas:
if (currentHeight < maxHeight) vcanvas.height = currentHeight;
vctx.drawImage(canvas, 0, 0, canvas.width, currentHeight,
0, 0, canvas.width, currentHeight);
if (currentHeight < maxHeight) setTimeout(loop, 50);
})();
body{background:#eee} canvas{background:#fff}
canvas{border:1px solid #000}
<canvas></canvas>
1) If you are dealing with "infinite" height you want to split the process into blocks. Set internal canvas to block size, when exceeded enlarge internal canvas with new block size - but here you will have to do full redraw and setup again.
And this leads to option 2: The latter technique can be used directly with the visual canvas as well, and you can use CSS to clip it putting it inside a div element which you style with height and overflow:hidden.
Example 2
var canvas = document.querySelector("canvas"),
ctx = canvas.getContext("2d"),
div = document.querySelector("div"),
line = 25,
maxHeight = 7 * line,
currentHeight = line,
chars = "abcdefghijklmnowxyzABCD#/&%/(#)=!LMNOPQRSTUVWXYZ".split(""),
x = 0, y = 0, ch;
// set canvas to max height, div to clip height
canvas.height = maxHeight;
div.style.height = currentHeight + "px";
// canvas setup
ctx.font = "20px sans-serif";
ctx.textBaseline = "top";
// draw someting to the canvas:
(function loop() {
ch = chars[(Math.random() * chars.length)|0];
ctx.fillStyle = "hsl(" + (360*Math.random()) + ",75%,50%)";
ctx.fillText(ch, x, y);
x += ctx.measureText(ch).width;
if (x > canvas.width) {
x = 0; y += line; currentHeight += line;
}
// copy to visual canvas:
if (currentHeight < maxHeight) {
div.style.height = currentHeight + "px"; // increase clipping height
setTimeout(loop, 50)
}
})();
body{background:#eee} canvas{background:#fff}
div{border:1px solid #000; overflow:hidden; width:302px}
<div><canvas></canvas></div>
Resizing the canvas element will always reset its context to the default style states. (.save & .restore will not let the context styles survive a resizing)
The common canvas pattern to deal with changes to canvas content is:
Save data (eg your company)
Either save context styles as javascript variables or embed the style changes in functions (eg a specialized function to set appropriate styles and redraw the company heading).
Resize (or clear) the canvas
Redraw all the content using the saved data and saved styles/functions.
Example code:
Click "Full page" to see full receipt
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var lineSpace=20;
var leftOfForm=25;
var topOfForm=80;
var testingItems=110;
var company={
name:'MyCompany, Inc',
address:{
address_line_1: '123 Main Street',
city:'Anytown, Anywhere',
postcode:'12345'
}
}
var lineItems=[];
addLineItem(testingItems,'Description of sku#'+testingItems,testingItems);
$('#test').click(function(){
testingItems++;
addLineItem(testingItems,'Description of sku#'+testingItems,testingItems);
draw();
});
draw();
function addLineItem(sku,description,price){
lineItems.push({
sku:sku,
description:description,
price:price
});
}
function draw(){
// note: changing canvas.height auto-erases the content also
canvas.height=topOfForm+(6+2+lineItems.length)*lineSpace;
ctx.strokeRect(0,0,canvas.width,canvas.height);
drawHeader(leftOfForm,topOfForm);
for(var i=0;i<lineItems.length;i++){
drawLineItem(lineItems[i],leftOfForm,topOfForm+(6+2+i)*lineSpace);
}
}
function drawHeader(left,cursor){
var cx=canvas.width/2;
var line=function(linecount){ return(cursor+lineSpace*linecount); }
ctx.save();
ctx.beginPath();
ctx.moveTo(left,line(0)-2);
ctx.lineTo(526,line(0)-2);
ctx.moveTo(left,line(1)-2);
ctx.lineTo(526,line(1)-2);
ctx.lineWidth=2;
ctx.stroke();
ctx.font='18px verdana';
ctx.textAlign='center';
ctx.textBaseline='top';
ctx.fillText(company.name,cx,line(2));
ctx.fillText(company.address.address_line_1,cx,line(3));
ctx.fillText(company.address.city,cx,line(4));
ctx.fillText(company.address.postcode,cx,line(5));
ctx.restore();
}
function drawLineItem(item,left,cursor){
ctx.save();
ctx.font='14px verdana';
ctx.textAlign='left';
ctx.textBaseline='top';
ctx.fillText(item.sku,left,cursor);
ctx.fillText(item.description,left+40,cursor);
ctx.textAlign='right';
ctx.fillText(item.price,left+450,cursor);
ctx.restore();
}
body{ background-color: ivory; padding:10px; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id=test>Add another line</button>
<br>
<canvas id="canvas" width=550 height=300></canvas>

HTML5 Canvas 2D Context -- How to reset the styles for the next drawing operation?

So, I've got my cute little canvas context:
var canvas = document.createElement('canvas');
canvas.width = 800;
canvas.height = 400;
var context = canvas.getContext('2d');
And I draw a line of text, with a shadow:
context.textAlign = "center";
context.font = "bold 20px sans-serif";
context.fillStyle = "#F90";
context.shadowColor = "black";
context.shadowBlur = 2;
context.shadowOffsetX = 2;
context.shadowOffsetY = 2;
context.fillText("Chivalry", canvas.width/2, 124);
Later on, I go on to draw something else:
context.textAlign = "center";
context.font = "bold 16px sans-serif";
context.fillStyle = "white";
context.fillText("Medieval Warfare", canvas.width/2, 144);
But I don't want that last one to have a shadow. Of course, the context's shadow style properties were never reset, and so the shadow is applied to all further drawings.
How should I restore the context's defaults?

Categories

Resources