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");
5
  • HTML5 canvas is raster-based, so it will always become blurred when zoomed. Would using SVG instead of canvas be an option? Commented Dec 29, 2015 at 7:01
  • With "take a printout" do you mean "print on paper"? Commented Dec 29, 2015 at 7: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. Commented Dec 29, 2015 at 7:23
  • @Philipp: Could you explain how would I loop the lines using javascript if I were to draw the graph using SVG? Commented Dec 29, 2015 at 8:19
  • @RobinBaby not in just 600 characters. Procedurally generating SVG is a completely different concept than using canvas. Commented Dec 29, 2015 at 8:31

2 Answers 2

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()

Sign up to request clarification or add additional context in comments.

Comments

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>

8 Comments

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 : jsfiddle.net/kkkfn7td
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.
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 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 : jsfiddle.net/kkkfn7td/3
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.
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 : jsfiddle.net/gamealchemist/drjny7ty/embedded/result :-)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.