Drawing crisp 1px lines on HTML5 canvas when using transformations
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):
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
add a comment |
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):
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
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
add a comment |
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):
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
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):
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
javascript html canvas html5-canvas
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
add a comment |
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
add a comment |
1 Answer
1
active
oldest
votes
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>
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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>
add a comment |
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>
add a comment |
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>
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>
edited Nov 14 '18 at 1:22
answered Nov 13 '18 at 23:53
KaiidoKaiido
40.3k460100
40.3k460100
add a comment |
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
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