1

I have the following javascript code to draw a graph sheet. But the problem is when I take a printout, The thin lines are not appearing sharp. The problem is visible when you zoom the html page. I want the lines to be more sharp. But the width should be the same. Is it possible? Please help.

function drawBkg(canvasElem, squareSize, minorLineWidthStr, lineColStr)
{
    var nLinesDone = 0;
    var i, curX, curY;
    var ctx = canvasElem.getContext('2d');
    ctx.clearRect(0,0,canvasElem.width,canvasElem.height);

    // draw the vertical lines
    curX=0;
    ctx.strokeStyle = lineColStr;
while (curX < canvasElem.width)
{

    if (nLinesDone % 5 == 0)
        ctx.lineWidth = 0.7;
    else
        ctx.lineWidth = minorLineWidthStr;

    ctx.beginPath();
    ctx.moveTo(curX, 0);
    ctx.lineTo(curX, canvasElem.height);
    ctx.stroke();

    curX += squareSize;
    nLinesDone++;
}

    // draw the horizontal lines
    curY=0;
    nLinesDone = 0;
    while (curY < canvasElem.height)
    {
        if (nLinesDone % 5 == 0)
            ctx.lineWidth = 0.7;
    else
        ctx.lineWidth = minorLineWidthStr;
    ctx.beginPath();
    ctx.moveTo(0, curY);
    ctx.lineTo(canvasElem.width, curY);
    ctx.stroke();

    curY += squareSize;
    nLinesDone++;
}
}

drawBkg(byId('canvas'), 3.78, "0.35", "green");
pikaaaaaaaaa
  • 95
  • 12
  • HTML5 canvas is raster-based, so it will always become blurred when zoomed. Would using SVG instead of canvas be an option? – Philipp Dec 29 '15 at 07:01
  • With "take a printout" do you mean "print on paper"? – Philipp Dec 29 '15 at 07:19
  • The canvas element works or widths based on pixel size at normal focus any sort of zooming or changing dimensions via css will automatically reduce the quality of the canvas what you'd need to do is write code that will scale the canvas when you scale the page. – Binvention Dec 29 '15 at 07:23
  • @Philipp: Could you explain how would I loop the lines using javascript if I were to draw the graph using SVG? – pikaaaaaaaaa Dec 29 '15 at 08:19
  • @RobinBaby not in just 600 characters. Procedurally generating SVG is a completely different concept than using canvas. – Philipp Dec 29 '15 at 08:31

2 Answers2

1

What you are experiencing is the difference between your screen's PPI and your printer's DPI.

Canvas output is a raster image, if you set its size to be like 96px, a monitor with a resolution of 96ppi will output it as a one inch large image, but a printer with 300ppi will output it as a 3.125 inch image.
When doing so, the printing operation will downsample your image so it can fit into this new size. (each pixel will be multiplied so it covers a bigger area).

But the canvas context2d has a scale() method, so if all your drawings are vector based1, you can :

  • create a bigger canvas before printing,
  • set its context's scale to the wanted factor,
  • call the same drawing as on the smaller canvas
  • if you are printing directly from the browser's "print the page", set the bigger canvas style.width and style.height properties to the width and height properties of the smaller one,
  • replace the smaller canvas node with the bigger one,
  • print,
  • replace the bigger canvas with the original one

For this, you will need to rewrite a little bit your function so it doesn't take the passed canvas' width/height as values, but rather values that you have chosen.

function drawBkg(ctx, width, height, squareSize, minorLineWidthStr, lineColStr) {
  var nLinesDone = 0;
  var i, curX, curY;
  ctx.clearRect(0, 0, width, height);

  // draw the vertical lines
  curX = 0;
  ctx.strokeStyle = lineColStr;
  while (curX < width) {
    if (nLinesDone % 5 == 0)
      ctx.lineWidth = 0.7;
    else
      ctx.lineWidth = minorLineWidthStr;
    ctx.beginPath();
    ctx.moveTo(curX, 0);
    ctx.lineTo(curX, height);
    ctx.stroke();
    curX += squareSize;
    nLinesDone++;
  }

  // draw the horizontal lines
  curY = 0;
  nLinesDone = 0;
  while (curY < height) {
    if (nLinesDone % 5 == 0)
      ctx.lineWidth = 0.7;
    else
      ctx.lineWidth = minorLineWidthStr;
    ctx.beginPath();
    ctx.moveTo(0, curY);
    ctx.lineTo(width, curY);
    ctx.stroke();

    curY += squareSize;
    nLinesDone++;
  }
}


// your drawings
var smallCanvas = document.getElementById('smallCanvas');
var smallCtx = smallCanvas.getContext('2d');
drawBkg(smallCtx, smallCanvas.width, smallCanvas.height, 3.78, "0.35", "green");


// a function to get the screen's ppi
function getPPI() {
  var test = document.createElement('div');
  test.style.width = "1in";
  test.style.height = 0;
  document.body.appendChild(test);
  var dpi = devicePixelRatio || 1;
  var ppi = parseInt(getComputedStyle(test).width) * dpi;
  document.body.removeChild(test);
  return ppi;
}

function scaleAndPrint(outputDPI) {
  var factor = outputDPI / getPPI();
  var bigCanvas = smallCanvas.cloneNode();
  // set the required size of our "printer version" canvas
  bigCanvas.width = smallCanvas.width * factor;
  bigCanvas.height = smallCanvas.height * factor;
  // set the display size the same as the original one to don't brake the page's layout
  var rect = smallCanvas.getBoundingClientRect();
  bigCanvas.style.width = rect.width + 'px';
  bigCanvas.style.height = rect.height + 'px';
  var bigCtx = bigCanvas.getContext('2d');

  // change the scale of our big context
  bigCtx.scale(factor, factor);

  // tell the function we want the height and width of the small canvas
  drawBkg(bigCtx, smallCanvas.width, smallCanvas.height, 3.78, "0.35", "green");
  // replace our original canvas with the bigger one
  smallCanvas.parentNode.replaceChild(bigCanvas, smallCanvas);
  // call the printer
  print();
  // set the original one back
  bigCanvas.parentNode.replaceChild(smallCanvas, bigCanvas);
}

btn_o.onclick = function() { print(); };
btn_s.onclick = function() { scaleAndPrint(300);};
<button id="btn_o">print without scaling</button>
<button id="btn_s">print with scaling</button>
<br>
<canvas id="smallCanvas" width="250" height="500"></canvas>

1. all drawing operations on canvas are vector based, except for drawImage(), and putImageData()

Kaiido
  • 123,334
  • 13
  • 219
  • 285
0

Most simple way to achieve cripser lines is to use oversampling : you draw in a canvas which has a resolution bigger than the screen's resolution.

In Javascript if you want to oversample by a factor of X :

  • Change canvas's width and height to width*X and height*X
  • Scale the canvas's context by a factor of X
  • Fix Css width and height to inital width and height to keep same size on screen.

In the below sample i first downsampled the canvas to make it easier to see. You have to zoom quite a lot to see the difference between no upsampling, 2 X and 4X.

function overSampleCanvas(tgtCanvas, ctx, factor) {
  var width = tgtCanvas.width;
  var height = tgtCanvas.height;
  tgtCanvas.width = 0 | (width * factor);
  tgtCanvas.height = 0 | (height * factor);
  tgtCanvas.style.width = width + 'px';
  tgtCanvas.style.height = height + 'px';
  ctx.scale(factor, factor);
}

// -------------------- example

var $ = document.getElementById.bind(document);

var cv05 = $('cv05'),
  ctx05 = cv05.getContext('2d');
var cv = $('cv'),
  ctx = cv.getContext('2d');
var cv2X = $('cv2X'),
  ctx2X = cv2X.getContext('2d');
var cv4X = $('cv4X'),
  ctx4X = cv4X.getContext('2d');

overSampleCanvas(cv05, ctx05, 0.5);
overSampleCanvas(cv2X, ctx2X, 2);
overSampleCanvas(cv4X, ctx4X, 4);


function drawCircle(ctx) {
  ctx.beginPath();
  ctx.arc(100, 100, 50, 0, 6.28);
  ctx.fillStyle = '#AB6';
  ctx.fill();
}

drawCircle(ctx05);
drawCircle(ctx);
drawCircle(ctx2X);
drawCircle(ctx4X);
 canvas downsampled by 2X, normal, then upsampled by 2X, then 4X. <br>

<canvas id="cv05" width="100" height="100"></canvas>
<canvas id="cv" width="100" height="100"></canvas>
<canvas id="cv2X" width="100" height="100"></canvas>
<canvas id="cv4X" width="100" height="100"></canvas>
GameAlchemist
  • 18,995
  • 7
  • 36
  • 59
  • The core solution is almost the same as my answer isn't it? But I didn't wanted to show the CSS part because actually, CSS resampling isn't perfect on screen (but it's obviously the way to go before printing), even on oversampling. See the result of this on OP's graphic : https://jsfiddle.net/kkkfn7td/ – Kaiido Dec 30 '15 at 03:31
  • 1) It is important, not to break an existing site, to keep the canvas original size on screen. 2) Your example is not really relevant : 50px X 50px is just too small that's all. 3) Anyway resampling display artifacts don't matter when the purpose of the canvas is to be printed. – GameAlchemist Dec 30 '15 at 04:36
  • 1) Totally agreed, but we didn't have any info on how OP does print the canvas (does he save the image locally then print it or does he directly print the webpage from his browser ?) 2) how isn't it relevant? It's not the size of the canvas which is relevant, but the size of the drawings, here OP is drawing a graph paper, if it is relevant for 50px, [it will also be for 500px](https://jsfiddle.net/kkkfn7td/2/) and even if we take the most relevant case for OP, which is the 96 screen dpi standard to 300 professional printers standard, it's still relevant : https://jsfiddle.net/kkkfn7td/3/ – Kaiido Dec 30 '15 at 05:07
  • 3) once again agreed, that's why in my answer I clearly stated "create a bigger canvas **before printing**," and didn't shown how to fit it on screen since it shouldn't be kept on screen after the printing. But I will edit it to make it clearer. – Kaiido Dec 30 '15 at 05:09
  • PS : for 1), you may actually break the site layout, since you are applying the original canvas width property as the rescaled's style property, but if the original one's also had it's style.width set, it won't be applied to the scaled one and they won't fit anymore. – Kaiido Dec 30 '15 at 06:12
  • I just wanted to provide a generic solution to oversample an existing canvas without changing anything to the html or code. Eot. – GameAlchemist Dec 30 '15 at 06:54
  • But OP's code had to be modified, since it was taking the canvas as an argument, and defined the drawing width/height based on this canvasElement. Except from that I don't modify anything either (and the update I did does even reset the original canvas into the page after the printing operation). I think that what is important in all this flow of comments, is that **oversampling is not a good thing on screens**, we should draw at device's resolution, otherwise there will be more anti-aliasing than needed and we don't want it, do we? – Kaiido Dec 30 '15 at 07:00
  • 1
    I already explained my intents above, and i'm not the one you should convince : rather the O.P. or people interested in printing. And well for oversampling not being a good thing... besides 'it depends', all my screens here are UHD/retina so i use 2X oversampling quite often :-). Again i don't want to spend too much time here, i have a war to build : https://jsfiddle.net/gamealchemist/drjny7ty/embedded/result/ :-) – GameAlchemist Dec 30 '15 at 10:09