Drawing crisp 1px lines on HTML5 canvas when using transformations












0















I'm drawing a lot of 1px lines on an HTML5 canvas element in my code. The drawing code looks roughly like the following, the transform variable in this case is set using d3-zoom. instructions.f32 is a Float32Array that contains the coordinates I use for drawing the lines.



 context.setTransform(
transform.k,
0,
0,
transform.k,
transform.x,
transform.y
);
context.lineWidth = 1 / transform.k;
context.beginPath();
for (let i = from; i < to; ++i) {

let v1 = instructions.f32[i * 4 + 1];
let v2 = instructions.f32[i * 4 + 2];

// execute some moveTo/lineTo commands using v1 and v2 as coordinates
}
context.stroke();


One issue with this code is that the 1px lines are blurry because I'm drawing across pixel boundaries. I tried to adapt the code to snap the lines to the nearest pixels like the following:



let v1 = (Math.round(instructions.f32[i * 4 + 1] * transform.k) + 0.5) / transform.k;
let v2 = (Math.round(instructions.f32[i * 4 + 2] * transform.k) + 0.5) / transform.k;


But that still results in blurry lines like the following image (screenshot of a zoomed in image):



enter image description here



If I didn't have any transformation set, as far as I understand I would simply have to round the coordinates to the nearest pixel and add 0.5 to get crisp lines. But I'm not sure how to achieve this when my entire canvas is transformed, and I'm not drawing into the final coordinate system. As my attempts to correct for this have failed so far, it looks like I'm missing something here, or making some other mistake on the way.



How can I draw crisp 1px lines in canvas when transforming my entire canvas with setTransform? How exactly do I have to round the coordinates to snap the resulting lines to pixels?










share|improve this question

























  • Possible duplicate of Drawing a 1px thick line in canvas creates a 2px thick line

    – Jack Bashford
    Nov 13 '18 at 20:21











  • @JackBashford that is not an exact duplicate. I'm asking about the case where the canvas is transformed, so it's not as easy as the solutions in that linked questions.

    – Mad Scientist
    Nov 13 '18 at 20:24
















0















I'm drawing a lot of 1px lines on an HTML5 canvas element in my code. The drawing code looks roughly like the following, the transform variable in this case is set using d3-zoom. instructions.f32 is a Float32Array that contains the coordinates I use for drawing the lines.



 context.setTransform(
transform.k,
0,
0,
transform.k,
transform.x,
transform.y
);
context.lineWidth = 1 / transform.k;
context.beginPath();
for (let i = from; i < to; ++i) {

let v1 = instructions.f32[i * 4 + 1];
let v2 = instructions.f32[i * 4 + 2];

// execute some moveTo/lineTo commands using v1 and v2 as coordinates
}
context.stroke();


One issue with this code is that the 1px lines are blurry because I'm drawing across pixel boundaries. I tried to adapt the code to snap the lines to the nearest pixels like the following:



let v1 = (Math.round(instructions.f32[i * 4 + 1] * transform.k) + 0.5) / transform.k;
let v2 = (Math.round(instructions.f32[i * 4 + 2] * transform.k) + 0.5) / transform.k;


But that still results in blurry lines like the following image (screenshot of a zoomed in image):



enter image description here



If I didn't have any transformation set, as far as I understand I would simply have to round the coordinates to the nearest pixel and add 0.5 to get crisp lines. But I'm not sure how to achieve this when my entire canvas is transformed, and I'm not drawing into the final coordinate system. As my attempts to correct for this have failed so far, it looks like I'm missing something here, or making some other mistake on the way.



How can I draw crisp 1px lines in canvas when transforming my entire canvas with setTransform? How exactly do I have to round the coordinates to snap the resulting lines to pixels?










share|improve this question

























  • Possible duplicate of Drawing a 1px thick line in canvas creates a 2px thick line

    – Jack Bashford
    Nov 13 '18 at 20:21











  • @JackBashford that is not an exact duplicate. I'm asking about the case where the canvas is transformed, so it's not as easy as the solutions in that linked questions.

    – Mad Scientist
    Nov 13 '18 at 20:24














0












0








0








I'm drawing a lot of 1px lines on an HTML5 canvas element in my code. The drawing code looks roughly like the following, the transform variable in this case is set using d3-zoom. instructions.f32 is a Float32Array that contains the coordinates I use for drawing the lines.



 context.setTransform(
transform.k,
0,
0,
transform.k,
transform.x,
transform.y
);
context.lineWidth = 1 / transform.k;
context.beginPath();
for (let i = from; i < to; ++i) {

let v1 = instructions.f32[i * 4 + 1];
let v2 = instructions.f32[i * 4 + 2];

// execute some moveTo/lineTo commands using v1 and v2 as coordinates
}
context.stroke();


One issue with this code is that the 1px lines are blurry because I'm drawing across pixel boundaries. I tried to adapt the code to snap the lines to the nearest pixels like the following:



let v1 = (Math.round(instructions.f32[i * 4 + 1] * transform.k) + 0.5) / transform.k;
let v2 = (Math.round(instructions.f32[i * 4 + 2] * transform.k) + 0.5) / transform.k;


But that still results in blurry lines like the following image (screenshot of a zoomed in image):



enter image description here



If I didn't have any transformation set, as far as I understand I would simply have to round the coordinates to the nearest pixel and add 0.5 to get crisp lines. But I'm not sure how to achieve this when my entire canvas is transformed, and I'm not drawing into the final coordinate system. As my attempts to correct for this have failed so far, it looks like I'm missing something here, or making some other mistake on the way.



How can I draw crisp 1px lines in canvas when transforming my entire canvas with setTransform? How exactly do I have to round the coordinates to snap the resulting lines to pixels?










share|improve this question
















I'm drawing a lot of 1px lines on an HTML5 canvas element in my code. The drawing code looks roughly like the following, the transform variable in this case is set using d3-zoom. instructions.f32 is a Float32Array that contains the coordinates I use for drawing the lines.



 context.setTransform(
transform.k,
0,
0,
transform.k,
transform.x,
transform.y
);
context.lineWidth = 1 / transform.k;
context.beginPath();
for (let i = from; i < to; ++i) {

let v1 = instructions.f32[i * 4 + 1];
let v2 = instructions.f32[i * 4 + 2];

// execute some moveTo/lineTo commands using v1 and v2 as coordinates
}
context.stroke();


One issue with this code is that the 1px lines are blurry because I'm drawing across pixel boundaries. I tried to adapt the code to snap the lines to the nearest pixels like the following:



let v1 = (Math.round(instructions.f32[i * 4 + 1] * transform.k) + 0.5) / transform.k;
let v2 = (Math.round(instructions.f32[i * 4 + 2] * transform.k) + 0.5) / transform.k;


But that still results in blurry lines like the following image (screenshot of a zoomed in image):



enter image description here



If I didn't have any transformation set, as far as I understand I would simply have to round the coordinates to the nearest pixel and add 0.5 to get crisp lines. But I'm not sure how to achieve this when my entire canvas is transformed, and I'm not drawing into the final coordinate system. As my attempts to correct for this have failed so far, it looks like I'm missing something here, or making some other mistake on the way.



How can I draw crisp 1px lines in canvas when transforming my entire canvas with setTransform? How exactly do I have to round the coordinates to snap the resulting lines to pixels?







javascript html canvas html5-canvas






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 13 '18 at 22:09







Mad Scientist

















asked Nov 13 '18 at 20:05









Mad ScientistMad Scientist

11.1k96285




11.1k96285













  • Possible duplicate of Drawing a 1px thick line in canvas creates a 2px thick line

    – Jack Bashford
    Nov 13 '18 at 20:21











  • @JackBashford that is not an exact duplicate. I'm asking about the case where the canvas is transformed, so it's not as easy as the solutions in that linked questions.

    – Mad Scientist
    Nov 13 '18 at 20:24



















  • Possible duplicate of Drawing a 1px thick line in canvas creates a 2px thick line

    – Jack Bashford
    Nov 13 '18 at 20:21











  • @JackBashford that is not an exact duplicate. I'm asking about the case where the canvas is transformed, so it's not as easy as the solutions in that linked questions.

    – Mad Scientist
    Nov 13 '18 at 20:24

















Possible duplicate of Drawing a 1px thick line in canvas creates a 2px thick line

– Jack Bashford
Nov 13 '18 at 20:21





Possible duplicate of Drawing a 1px thick line in canvas creates a 2px thick line

– Jack Bashford
Nov 13 '18 at 20:21













@JackBashford that is not an exact duplicate. I'm asking about the case where the canvas is transformed, so it's not as easy as the solutions in that linked questions.

– Mad Scientist
Nov 13 '18 at 20:24





@JackBashford that is not an exact duplicate. I'm asking about the case where the canvas is transformed, so it's not as easy as the solutions in that linked questions.

– Mad Scientist
Nov 13 '18 at 20:24












1 Answer
1






active

oldest

votes


















0














Since it seems your transform doesn't have a skew or rotate property, the easiest will probably be to not transform your context, but rather scale and translate all the coordinates.



Currently, you are setting the lineWidth to 1 / zoom, given how compiters are good with Math precision, you will have hard time ending up drawing a perfect 1px stroke with that, only a few zoom values will, and if you wish to restrain your zoom to these values, you'll get choppy zoom.



Instead always keep the lineWidth at 1px, scale and translate all coords, before rounding these to the nearest pixel boundary.



context.setTransform(1,0,0,1,0,0);
context.lineWidth = 1;
context.beginPath();
for (let i = from; i < to; ++i) {

let v1 = instructions.f32[i * 4 + 1];
let v2 = instructions.f32[i * 4 + 2];
// scale and translate
v1 = (v1 + transform.x) * transform.k;
v2 = (v2 + transform.y) * transfrom.k;
// round
const r1 = Math.round(v1);
const r2 = Math.round(v2);
// to nearest px boundary
v1 = r1 + (0.5 * Math.sign(r1 - v1) || 0.5);
v2 = r2 + (0.5 * Math.sign(r2 - v2) || 0.5);

// lineTo...
}





const pts = [60, 60, 60, 110, 100,110, 100, 90, 220, 90];
const zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
const transform = {k: 1, x: 0, y: 0};
const context = canvas.getContext('2d');
d3.select('canvas')
.call(zoom);
draw();
function draw() {

context.setTransform(1,0,0,1,0,0);
context.clearRect(0,0,canvas.width, canvas.height);
context.lineWidth = 1;
context.beginPath();
for (let i = 0; i < pts.length; i+=2) {

let v1 = pts[i];
let v2 = pts[i + 1];
// scale and translate
v1 = (v1 + transform.x) * transform.k;
v2 = (v2 + transform.y) * transform.k;
// round
const r1 = Math.round(v1);
const r2 = Math.round(v2);
// to nearest px boundary
v1 = r1 + (0.5 * Math.sign(r1 - v1) || 0.5);
v2 = r2 + (0.5 * Math.sign(r2 - v2) || 0.5);

context.lineTo(v1, v2);
}
context.stroke();
}
function zoomed() {
const evt = d3.event;
transform.k = evt.scale;
transform.x = evt.translate[0];
transform.y = evt.translate[1];
draw();
}

canvas {border: 1px solid}

zoom with mousewheel and pan by dragging<br>
<canvas id="canvas"></canvas>
<script src="//d3js.org/d3.v3.min.js"></script>





But you may prefer less precise but also less jagged and simpler flooring:



  v1 = (v1 + transform.x) * transform.k;
v2 = (v2 + transform.y) * transform.k;
// floor
v1 = Math.floor(v1) + 0.5;
v2 = Math.floor(v2) + 0.5;

// lineTo





const pts = [60, 60, 60, 110, 100,110, 100, 90, 220, 90];
const zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
const transform = {k: 1, x: 0, y: 0};
const context = canvas.getContext('2d');
d3.select('canvas')
.call(zoom);
draw();
function draw() {

context.setTransform(1,0,0,1,0,0);
context.clearRect(0,0,canvas.width, canvas.height);
context.lineWidth = 1;
context.beginPath();
for (let i = 0; i < pts.length; i+=2) {

let v1 = pts[i];
let v2 = pts[i + 1];
// scale and translate
v1 = (v1 + transform.x) * transform.k;
v2 = (v2 + transform.y) * transform.k;
// floor
v1 = Math.floor(v1) + 0.5;
v2 = Math.floor(v2) + 0.5;
context.lineTo(v1, v2);
}
context.stroke();
}
function zoomed() {
const evt = d3.event;
transform.k = evt.scale;
transform.x = evt.translate[0];
transform.y = evt.translate[1];
draw();
}

canvas {border: 1px solid}

zoom with mousewheel and pan by dragging<br>
<canvas id="canvas"></canvas>
<script src="//d3js.org/d3.v3.min.js"></script>








share|improve this answer

























    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53288682%2fdrawing-crisp-1px-lines-on-html5-canvas-when-using-transformations%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    0














    Since it seems your transform doesn't have a skew or rotate property, the easiest will probably be to not transform your context, but rather scale and translate all the coordinates.



    Currently, you are setting the lineWidth to 1 / zoom, given how compiters are good with Math precision, you will have hard time ending up drawing a perfect 1px stroke with that, only a few zoom values will, and if you wish to restrain your zoom to these values, you'll get choppy zoom.



    Instead always keep the lineWidth at 1px, scale and translate all coords, before rounding these to the nearest pixel boundary.



    context.setTransform(1,0,0,1,0,0);
    context.lineWidth = 1;
    context.beginPath();
    for (let i = from; i < to; ++i) {

    let v1 = instructions.f32[i * 4 + 1];
    let v2 = instructions.f32[i * 4 + 2];
    // scale and translate
    v1 = (v1 + transform.x) * transform.k;
    v2 = (v2 + transform.y) * transfrom.k;
    // round
    const r1 = Math.round(v1);
    const r2 = Math.round(v2);
    // to nearest px boundary
    v1 = r1 + (0.5 * Math.sign(r1 - v1) || 0.5);
    v2 = r2 + (0.5 * Math.sign(r2 - v2) || 0.5);

    // lineTo...
    }





    const pts = [60, 60, 60, 110, 100,110, 100, 90, 220, 90];
    const zoom = d3.behavior.zoom()
    .scaleExtent([1, 10])
    .on("zoom", zoomed);
    const transform = {k: 1, x: 0, y: 0};
    const context = canvas.getContext('2d');
    d3.select('canvas')
    .call(zoom);
    draw();
    function draw() {

    context.setTransform(1,0,0,1,0,0);
    context.clearRect(0,0,canvas.width, canvas.height);
    context.lineWidth = 1;
    context.beginPath();
    for (let i = 0; i < pts.length; i+=2) {

    let v1 = pts[i];
    let v2 = pts[i + 1];
    // scale and translate
    v1 = (v1 + transform.x) * transform.k;
    v2 = (v2 + transform.y) * transform.k;
    // round
    const r1 = Math.round(v1);
    const r2 = Math.round(v2);
    // to nearest px boundary
    v1 = r1 + (0.5 * Math.sign(r1 - v1) || 0.5);
    v2 = r2 + (0.5 * Math.sign(r2 - v2) || 0.5);

    context.lineTo(v1, v2);
    }
    context.stroke();
    }
    function zoomed() {
    const evt = d3.event;
    transform.k = evt.scale;
    transform.x = evt.translate[0];
    transform.y = evt.translate[1];
    draw();
    }

    canvas {border: 1px solid}

    zoom with mousewheel and pan by dragging<br>
    <canvas id="canvas"></canvas>
    <script src="//d3js.org/d3.v3.min.js"></script>





    But you may prefer less precise but also less jagged and simpler flooring:



      v1 = (v1 + transform.x) * transform.k;
    v2 = (v2 + transform.y) * transform.k;
    // floor
    v1 = Math.floor(v1) + 0.5;
    v2 = Math.floor(v2) + 0.5;

    // lineTo





    const pts = [60, 60, 60, 110, 100,110, 100, 90, 220, 90];
    const zoom = d3.behavior.zoom()
    .scaleExtent([1, 10])
    .on("zoom", zoomed);
    const transform = {k: 1, x: 0, y: 0};
    const context = canvas.getContext('2d');
    d3.select('canvas')
    .call(zoom);
    draw();
    function draw() {

    context.setTransform(1,0,0,1,0,0);
    context.clearRect(0,0,canvas.width, canvas.height);
    context.lineWidth = 1;
    context.beginPath();
    for (let i = 0; i < pts.length; i+=2) {

    let v1 = pts[i];
    let v2 = pts[i + 1];
    // scale and translate
    v1 = (v1 + transform.x) * transform.k;
    v2 = (v2 + transform.y) * transform.k;
    // floor
    v1 = Math.floor(v1) + 0.5;
    v2 = Math.floor(v2) + 0.5;
    context.lineTo(v1, v2);
    }
    context.stroke();
    }
    function zoomed() {
    const evt = d3.event;
    transform.k = evt.scale;
    transform.x = evt.translate[0];
    transform.y = evt.translate[1];
    draw();
    }

    canvas {border: 1px solid}

    zoom with mousewheel and pan by dragging<br>
    <canvas id="canvas"></canvas>
    <script src="//d3js.org/d3.v3.min.js"></script>








    share|improve this answer






























      0














      Since it seems your transform doesn't have a skew or rotate property, the easiest will probably be to not transform your context, but rather scale and translate all the coordinates.



      Currently, you are setting the lineWidth to 1 / zoom, given how compiters are good with Math precision, you will have hard time ending up drawing a perfect 1px stroke with that, only a few zoom values will, and if you wish to restrain your zoom to these values, you'll get choppy zoom.



      Instead always keep the lineWidth at 1px, scale and translate all coords, before rounding these to the nearest pixel boundary.



      context.setTransform(1,0,0,1,0,0);
      context.lineWidth = 1;
      context.beginPath();
      for (let i = from; i < to; ++i) {

      let v1 = instructions.f32[i * 4 + 1];
      let v2 = instructions.f32[i * 4 + 2];
      // scale and translate
      v1 = (v1 + transform.x) * transform.k;
      v2 = (v2 + transform.y) * transfrom.k;
      // round
      const r1 = Math.round(v1);
      const r2 = Math.round(v2);
      // to nearest px boundary
      v1 = r1 + (0.5 * Math.sign(r1 - v1) || 0.5);
      v2 = r2 + (0.5 * Math.sign(r2 - v2) || 0.5);

      // lineTo...
      }





      const pts = [60, 60, 60, 110, 100,110, 100, 90, 220, 90];
      const zoom = d3.behavior.zoom()
      .scaleExtent([1, 10])
      .on("zoom", zoomed);
      const transform = {k: 1, x: 0, y: 0};
      const context = canvas.getContext('2d');
      d3.select('canvas')
      .call(zoom);
      draw();
      function draw() {

      context.setTransform(1,0,0,1,0,0);
      context.clearRect(0,0,canvas.width, canvas.height);
      context.lineWidth = 1;
      context.beginPath();
      for (let i = 0; i < pts.length; i+=2) {

      let v1 = pts[i];
      let v2 = pts[i + 1];
      // scale and translate
      v1 = (v1 + transform.x) * transform.k;
      v2 = (v2 + transform.y) * transform.k;
      // round
      const r1 = Math.round(v1);
      const r2 = Math.round(v2);
      // to nearest px boundary
      v1 = r1 + (0.5 * Math.sign(r1 - v1) || 0.5);
      v2 = r2 + (0.5 * Math.sign(r2 - v2) || 0.5);

      context.lineTo(v1, v2);
      }
      context.stroke();
      }
      function zoomed() {
      const evt = d3.event;
      transform.k = evt.scale;
      transform.x = evt.translate[0];
      transform.y = evt.translate[1];
      draw();
      }

      canvas {border: 1px solid}

      zoom with mousewheel and pan by dragging<br>
      <canvas id="canvas"></canvas>
      <script src="//d3js.org/d3.v3.min.js"></script>





      But you may prefer less precise but also less jagged and simpler flooring:



        v1 = (v1 + transform.x) * transform.k;
      v2 = (v2 + transform.y) * transform.k;
      // floor
      v1 = Math.floor(v1) + 0.5;
      v2 = Math.floor(v2) + 0.5;

      // lineTo





      const pts = [60, 60, 60, 110, 100,110, 100, 90, 220, 90];
      const zoom = d3.behavior.zoom()
      .scaleExtent([1, 10])
      .on("zoom", zoomed);
      const transform = {k: 1, x: 0, y: 0};
      const context = canvas.getContext('2d');
      d3.select('canvas')
      .call(zoom);
      draw();
      function draw() {

      context.setTransform(1,0,0,1,0,0);
      context.clearRect(0,0,canvas.width, canvas.height);
      context.lineWidth = 1;
      context.beginPath();
      for (let i = 0; i < pts.length; i+=2) {

      let v1 = pts[i];
      let v2 = pts[i + 1];
      // scale and translate
      v1 = (v1 + transform.x) * transform.k;
      v2 = (v2 + transform.y) * transform.k;
      // floor
      v1 = Math.floor(v1) + 0.5;
      v2 = Math.floor(v2) + 0.5;
      context.lineTo(v1, v2);
      }
      context.stroke();
      }
      function zoomed() {
      const evt = d3.event;
      transform.k = evt.scale;
      transform.x = evt.translate[0];
      transform.y = evt.translate[1];
      draw();
      }

      canvas {border: 1px solid}

      zoom with mousewheel and pan by dragging<br>
      <canvas id="canvas"></canvas>
      <script src="//d3js.org/d3.v3.min.js"></script>








      share|improve this answer




























        0












        0








        0







        Since it seems your transform doesn't have a skew or rotate property, the easiest will probably be to not transform your context, but rather scale and translate all the coordinates.



        Currently, you are setting the lineWidth to 1 / zoom, given how compiters are good with Math precision, you will have hard time ending up drawing a perfect 1px stroke with that, only a few zoom values will, and if you wish to restrain your zoom to these values, you'll get choppy zoom.



        Instead always keep the lineWidth at 1px, scale and translate all coords, before rounding these to the nearest pixel boundary.



        context.setTransform(1,0,0,1,0,0);
        context.lineWidth = 1;
        context.beginPath();
        for (let i = from; i < to; ++i) {

        let v1 = instructions.f32[i * 4 + 1];
        let v2 = instructions.f32[i * 4 + 2];
        // scale and translate
        v1 = (v1 + transform.x) * transform.k;
        v2 = (v2 + transform.y) * transfrom.k;
        // round
        const r1 = Math.round(v1);
        const r2 = Math.round(v2);
        // to nearest px boundary
        v1 = r1 + (0.5 * Math.sign(r1 - v1) || 0.5);
        v2 = r2 + (0.5 * Math.sign(r2 - v2) || 0.5);

        // lineTo...
        }





        const pts = [60, 60, 60, 110, 100,110, 100, 90, 220, 90];
        const zoom = d3.behavior.zoom()
        .scaleExtent([1, 10])
        .on("zoom", zoomed);
        const transform = {k: 1, x: 0, y: 0};
        const context = canvas.getContext('2d');
        d3.select('canvas')
        .call(zoom);
        draw();
        function draw() {

        context.setTransform(1,0,0,1,0,0);
        context.clearRect(0,0,canvas.width, canvas.height);
        context.lineWidth = 1;
        context.beginPath();
        for (let i = 0; i < pts.length; i+=2) {

        let v1 = pts[i];
        let v2 = pts[i + 1];
        // scale and translate
        v1 = (v1 + transform.x) * transform.k;
        v2 = (v2 + transform.y) * transform.k;
        // round
        const r1 = Math.round(v1);
        const r2 = Math.round(v2);
        // to nearest px boundary
        v1 = r1 + (0.5 * Math.sign(r1 - v1) || 0.5);
        v2 = r2 + (0.5 * Math.sign(r2 - v2) || 0.5);

        context.lineTo(v1, v2);
        }
        context.stroke();
        }
        function zoomed() {
        const evt = d3.event;
        transform.k = evt.scale;
        transform.x = evt.translate[0];
        transform.y = evt.translate[1];
        draw();
        }

        canvas {border: 1px solid}

        zoom with mousewheel and pan by dragging<br>
        <canvas id="canvas"></canvas>
        <script src="//d3js.org/d3.v3.min.js"></script>





        But you may prefer less precise but also less jagged and simpler flooring:



          v1 = (v1 + transform.x) * transform.k;
        v2 = (v2 + transform.y) * transform.k;
        // floor
        v1 = Math.floor(v1) + 0.5;
        v2 = Math.floor(v2) + 0.5;

        // lineTo





        const pts = [60, 60, 60, 110, 100,110, 100, 90, 220, 90];
        const zoom = d3.behavior.zoom()
        .scaleExtent([1, 10])
        .on("zoom", zoomed);
        const transform = {k: 1, x: 0, y: 0};
        const context = canvas.getContext('2d');
        d3.select('canvas')
        .call(zoom);
        draw();
        function draw() {

        context.setTransform(1,0,0,1,0,0);
        context.clearRect(0,0,canvas.width, canvas.height);
        context.lineWidth = 1;
        context.beginPath();
        for (let i = 0; i < pts.length; i+=2) {

        let v1 = pts[i];
        let v2 = pts[i + 1];
        // scale and translate
        v1 = (v1 + transform.x) * transform.k;
        v2 = (v2 + transform.y) * transform.k;
        // floor
        v1 = Math.floor(v1) + 0.5;
        v2 = Math.floor(v2) + 0.5;
        context.lineTo(v1, v2);
        }
        context.stroke();
        }
        function zoomed() {
        const evt = d3.event;
        transform.k = evt.scale;
        transform.x = evt.translate[0];
        transform.y = evt.translate[1];
        draw();
        }

        canvas {border: 1px solid}

        zoom with mousewheel and pan by dragging<br>
        <canvas id="canvas"></canvas>
        <script src="//d3js.org/d3.v3.min.js"></script>








        share|improve this answer















        Since it seems your transform doesn't have a skew or rotate property, the easiest will probably be to not transform your context, but rather scale and translate all the coordinates.



        Currently, you are setting the lineWidth to 1 / zoom, given how compiters are good with Math precision, you will have hard time ending up drawing a perfect 1px stroke with that, only a few zoom values will, and if you wish to restrain your zoom to these values, you'll get choppy zoom.



        Instead always keep the lineWidth at 1px, scale and translate all coords, before rounding these to the nearest pixel boundary.



        context.setTransform(1,0,0,1,0,0);
        context.lineWidth = 1;
        context.beginPath();
        for (let i = from; i < to; ++i) {

        let v1 = instructions.f32[i * 4 + 1];
        let v2 = instructions.f32[i * 4 + 2];
        // scale and translate
        v1 = (v1 + transform.x) * transform.k;
        v2 = (v2 + transform.y) * transfrom.k;
        // round
        const r1 = Math.round(v1);
        const r2 = Math.round(v2);
        // to nearest px boundary
        v1 = r1 + (0.5 * Math.sign(r1 - v1) || 0.5);
        v2 = r2 + (0.5 * Math.sign(r2 - v2) || 0.5);

        // lineTo...
        }





        const pts = [60, 60, 60, 110, 100,110, 100, 90, 220, 90];
        const zoom = d3.behavior.zoom()
        .scaleExtent([1, 10])
        .on("zoom", zoomed);
        const transform = {k: 1, x: 0, y: 0};
        const context = canvas.getContext('2d');
        d3.select('canvas')
        .call(zoom);
        draw();
        function draw() {

        context.setTransform(1,0,0,1,0,0);
        context.clearRect(0,0,canvas.width, canvas.height);
        context.lineWidth = 1;
        context.beginPath();
        for (let i = 0; i < pts.length; i+=2) {

        let v1 = pts[i];
        let v2 = pts[i + 1];
        // scale and translate
        v1 = (v1 + transform.x) * transform.k;
        v2 = (v2 + transform.y) * transform.k;
        // round
        const r1 = Math.round(v1);
        const r2 = Math.round(v2);
        // to nearest px boundary
        v1 = r1 + (0.5 * Math.sign(r1 - v1) || 0.5);
        v2 = r2 + (0.5 * Math.sign(r2 - v2) || 0.5);

        context.lineTo(v1, v2);
        }
        context.stroke();
        }
        function zoomed() {
        const evt = d3.event;
        transform.k = evt.scale;
        transform.x = evt.translate[0];
        transform.y = evt.translate[1];
        draw();
        }

        canvas {border: 1px solid}

        zoom with mousewheel and pan by dragging<br>
        <canvas id="canvas"></canvas>
        <script src="//d3js.org/d3.v3.min.js"></script>





        But you may prefer less precise but also less jagged and simpler flooring:



          v1 = (v1 + transform.x) * transform.k;
        v2 = (v2 + transform.y) * transform.k;
        // floor
        v1 = Math.floor(v1) + 0.5;
        v2 = Math.floor(v2) + 0.5;

        // lineTo





        const pts = [60, 60, 60, 110, 100,110, 100, 90, 220, 90];
        const zoom = d3.behavior.zoom()
        .scaleExtent([1, 10])
        .on("zoom", zoomed);
        const transform = {k: 1, x: 0, y: 0};
        const context = canvas.getContext('2d');
        d3.select('canvas')
        .call(zoom);
        draw();
        function draw() {

        context.setTransform(1,0,0,1,0,0);
        context.clearRect(0,0,canvas.width, canvas.height);
        context.lineWidth = 1;
        context.beginPath();
        for (let i = 0; i < pts.length; i+=2) {

        let v1 = pts[i];
        let v2 = pts[i + 1];
        // scale and translate
        v1 = (v1 + transform.x) * transform.k;
        v2 = (v2 + transform.y) * transform.k;
        // floor
        v1 = Math.floor(v1) + 0.5;
        v2 = Math.floor(v2) + 0.5;
        context.lineTo(v1, v2);
        }
        context.stroke();
        }
        function zoomed() {
        const evt = d3.event;
        transform.k = evt.scale;
        transform.x = evt.translate[0];
        transform.y = evt.translate[1];
        draw();
        }

        canvas {border: 1px solid}

        zoom with mousewheel and pan by dragging<br>
        <canvas id="canvas"></canvas>
        <script src="//d3js.org/d3.v3.min.js"></script>








        const pts = [60, 60, 60, 110, 100,110, 100, 90, 220, 90];
        const zoom = d3.behavior.zoom()
        .scaleExtent([1, 10])
        .on("zoom", zoomed);
        const transform = {k: 1, x: 0, y: 0};
        const context = canvas.getContext('2d');
        d3.select('canvas')
        .call(zoom);
        draw();
        function draw() {

        context.setTransform(1,0,0,1,0,0);
        context.clearRect(0,0,canvas.width, canvas.height);
        context.lineWidth = 1;
        context.beginPath();
        for (let i = 0; i < pts.length; i+=2) {

        let v1 = pts[i];
        let v2 = pts[i + 1];
        // scale and translate
        v1 = (v1 + transform.x) * transform.k;
        v2 = (v2 + transform.y) * transform.k;
        // round
        const r1 = Math.round(v1);
        const r2 = Math.round(v2);
        // to nearest px boundary
        v1 = r1 + (0.5 * Math.sign(r1 - v1) || 0.5);
        v2 = r2 + (0.5 * Math.sign(r2 - v2) || 0.5);

        context.lineTo(v1, v2);
        }
        context.stroke();
        }
        function zoomed() {
        const evt = d3.event;
        transform.k = evt.scale;
        transform.x = evt.translate[0];
        transform.y = evt.translate[1];
        draw();
        }

        canvas {border: 1px solid}

        zoom with mousewheel and pan by dragging<br>
        <canvas id="canvas"></canvas>
        <script src="//d3js.org/d3.v3.min.js"></script>





        const pts = [60, 60, 60, 110, 100,110, 100, 90, 220, 90];
        const zoom = d3.behavior.zoom()
        .scaleExtent([1, 10])
        .on("zoom", zoomed);
        const transform = {k: 1, x: 0, y: 0};
        const context = canvas.getContext('2d');
        d3.select('canvas')
        .call(zoom);
        draw();
        function draw() {

        context.setTransform(1,0,0,1,0,0);
        context.clearRect(0,0,canvas.width, canvas.height);
        context.lineWidth = 1;
        context.beginPath();
        for (let i = 0; i < pts.length; i+=2) {

        let v1 = pts[i];
        let v2 = pts[i + 1];
        // scale and translate
        v1 = (v1 + transform.x) * transform.k;
        v2 = (v2 + transform.y) * transform.k;
        // round
        const r1 = Math.round(v1);
        const r2 = Math.round(v2);
        // to nearest px boundary
        v1 = r1 + (0.5 * Math.sign(r1 - v1) || 0.5);
        v2 = r2 + (0.5 * Math.sign(r2 - v2) || 0.5);

        context.lineTo(v1, v2);
        }
        context.stroke();
        }
        function zoomed() {
        const evt = d3.event;
        transform.k = evt.scale;
        transform.x = evt.translate[0];
        transform.y = evt.translate[1];
        draw();
        }

        canvas {border: 1px solid}

        zoom with mousewheel and pan by dragging<br>
        <canvas id="canvas"></canvas>
        <script src="//d3js.org/d3.v3.min.js"></script>





        const pts = [60, 60, 60, 110, 100,110, 100, 90, 220, 90];
        const zoom = d3.behavior.zoom()
        .scaleExtent([1, 10])
        .on("zoom", zoomed);
        const transform = {k: 1, x: 0, y: 0};
        const context = canvas.getContext('2d');
        d3.select('canvas')
        .call(zoom);
        draw();
        function draw() {

        context.setTransform(1,0,0,1,0,0);
        context.clearRect(0,0,canvas.width, canvas.height);
        context.lineWidth = 1;
        context.beginPath();
        for (let i = 0; i < pts.length; i+=2) {

        let v1 = pts[i];
        let v2 = pts[i + 1];
        // scale and translate
        v1 = (v1 + transform.x) * transform.k;
        v2 = (v2 + transform.y) * transform.k;
        // floor
        v1 = Math.floor(v1) + 0.5;
        v2 = Math.floor(v2) + 0.5;
        context.lineTo(v1, v2);
        }
        context.stroke();
        }
        function zoomed() {
        const evt = d3.event;
        transform.k = evt.scale;
        transform.x = evt.translate[0];
        transform.y = evt.translate[1];
        draw();
        }

        canvas {border: 1px solid}

        zoom with mousewheel and pan by dragging<br>
        <canvas id="canvas"></canvas>
        <script src="//d3js.org/d3.v3.min.js"></script>





        const pts = [60, 60, 60, 110, 100,110, 100, 90, 220, 90];
        const zoom = d3.behavior.zoom()
        .scaleExtent([1, 10])
        .on("zoom", zoomed);
        const transform = {k: 1, x: 0, y: 0};
        const context = canvas.getContext('2d');
        d3.select('canvas')
        .call(zoom);
        draw();
        function draw() {

        context.setTransform(1,0,0,1,0,0);
        context.clearRect(0,0,canvas.width, canvas.height);
        context.lineWidth = 1;
        context.beginPath();
        for (let i = 0; i < pts.length; i+=2) {

        let v1 = pts[i];
        let v2 = pts[i + 1];
        // scale and translate
        v1 = (v1 + transform.x) * transform.k;
        v2 = (v2 + transform.y) * transform.k;
        // floor
        v1 = Math.floor(v1) + 0.5;
        v2 = Math.floor(v2) + 0.5;
        context.lineTo(v1, v2);
        }
        context.stroke();
        }
        function zoomed() {
        const evt = d3.event;
        transform.k = evt.scale;
        transform.x = evt.translate[0];
        transform.y = evt.translate[1];
        draw();
        }

        canvas {border: 1px solid}

        zoom with mousewheel and pan by dragging<br>
        <canvas id="canvas"></canvas>
        <script src="//d3js.org/d3.v3.min.js"></script>






        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Nov 14 '18 at 1:22

























        answered Nov 13 '18 at 23:53









        KaiidoKaiido

        40.3k460100




        40.3k460100






























            draft saved

            draft discarded




















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53288682%2fdrawing-crisp-1px-lines-on-html5-canvas-when-using-transformations%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Xamarin.iOS Cant Deploy on Iphone

            Glorious Revolution

            Dulmage-Mendelsohn matrix decomposition in Python