What's the best way to set a single pixel in an HTML5 canvas?
There are two best contenders:
Create a 1×1 image data, set the color, and
putImageData
at the location:var id = myContext.createImageData(1,1); // only do this once per page
var d = id.data; // only do this once per page
d[0] = r;
d[1] = g;
d[2] = b;
d[3] = a;
myContext.putImageData( id, x, y );Use
fillRect()
to draw a pixel (there should be no aliasing issues):ctx.fillStyle = "rgba("+r+","+g+","+b+","+(a/255)+")";
ctx.fillRect( x, y, 1, 1 );
You can test the speed of these here: http://jsperf.com/setting-canvas-pixel/9 or here https://www.measurethat.net/Benchmarks/Show/1664/1
I recommend testing against browsers you care about for maximum speed. As of July 2017, fillRect()
is 5-6× faster on Firefox v54 and Chrome v59 (Win7x64).
Other, sillier alternatives are:
using
getImageData()/putImageData()
on the entire canvas; this is about 100× slower than other options.creating a custom image using a data url and using
drawImage()
to show it:var img = new Image;
img.src = "data:image/png;base64," + myPNGEncoder(r,g,b,a);
// Writing the PNGEncoder is left as an exercise for the readercreating another img or canvas filled with all the pixels you want and use
drawImage()
to blit just the pixel you want across. This would probably be very fast, but has the limitation that you need to pre-calculate the pixels you need.
Note that my tests do not attempt to save and restore the canvas context fillStyle
; this would slow down the fillRect()
performance. Also note that I am not starting with a clean slate or testing the exact same set of pixels for each test.
Complete Solution for Drawing 1 Pixel Line on HTML5 Canvas
The "wider" line you refer to results from anti-aliasing that's automatically done by the browser.
Anti-aliasing is used to display a visually less jagged line.
Short of drawing pixel-by-pixel, there's currently no way of disabling anti-aliasing drawn by the browser.
You can use Bresenham's line algorithm to draw your line by setting individual pixels. Of course, setting individual pixels results in lesser performance.
Here's example code and a Demo: http://jsfiddle.net/m1erickson/3j7hpng0/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
var data=imgData.data;
bline(50,50,250,250);
ctx.putImageData(imgData,0,0);
function setPixel(x,y){
var n=(y*canvas.width+x)*4;
data[n]=255;
data[n+1]=0;
data[n+2]=0;
data[n+3]=255;
}
// Refer to: http://rosettacode.org/wiki/Bitmap/Bresenham's_line_algorithm#JavaScript
function bline(x0, y0, x1, y1) {
var dx = Math.abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
var dy = Math.abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
var err = (dx>dy ? dx : -dy)/2;
while (true) {
setPixel(x0,y0);
if (x0 === x1 && y0 === y1) break;
var e2 = err;
if (e2 > -dx) { err -= dy; x0 += sx; }
if (e2 < dy) { err += dx; y0 += sy; }
}
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
Draw single pixel line in html5 canvas
Call your function with these coordinates instead: drawLine(30,30.5,300,30.5);
. Try it in jsFiddle.
The problem is that your color will be at an edge, so the color will be halfway in the pixel above the edge and halfway below the edge. If you set the position of the line in the middle of an integer, it will be drawn within a pixel line.
This picture (from the linked article below) illustrates it:
You can read more about this on Canvas tutorial: A lineWidth example.
Drawing crisp 1px lines on HTML5 canvas when using transformations
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>
Drawing a single pixel w/canvas
Something like this?
<canvas id="canvas" style="width:100px; height:100px" width="5" height="5"></canvas>
<script>
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
//Background
context.fillStyle = "#000";
context.fillRect(0, 0, canvas.width, canvas.height);
canvas.addEventListener("click", function(e) {
var x = Math.floor(e.x * canvas.width / parseInt(canvas.style.width));
var y = Math.floor(e.y * canvas.height / parseInt(canvas.style.height));
//Zoomed in red 'square'
context.fillStyle = "#F00";
context.fillRect(x, y, 1, 1);
}, true);
</script>
Edit: Added click functionality
Demo: http://jsfiddle.net/Cmpde/
Drawing a dot on HTML5 canvas
For performance reasons, don't draw a circle if you can avoid it. Just draw a rectangle with a width and height of one:
ctx.fillRect(10,10,1,1); // fill in the pixel at (10,10)
How to draw separate pixels efficiently on canvas context?
- Get your CanvasElement context using .getContext("2d")
- create a function (
drawRect
) that basically creates a rectangle of size atx
,y
coordinates inside your canvas - Create an array of the arguments-Array
x, y, w, h, color
to be passed to your function - Loop your array calling the
drawRect
function with the given arguments
const drawRect = (ctx, x, y, w=1, h=1, color="#fff") => {
ctx.fillStyle = color;
ctx.fillRect(x, y, w, h);
};
const size = 20;
const ctx = document.querySelector("#cvs").getContext("2d");
ctx.canvas.width = size;
ctx.canvas.height = size;
const myPixels = [
[8, 5],
[6, 18],
[10, 10],
[15, 13, 2, 2, "fuchsia"], // [x, y, w, h, color]
];
// 1. Draw black background
drawRect(ctx, 0, 0, size, size, "#000");
// 2. Draw individual whites
myPixels.forEach(args => drawRect(ctx, ...args));
canvas {
image-rendering: pixelated;
width: 10em;
}
<canvas id="cvs"></canvas>
HTML5 canvas line width less that 1 pixel
Although it doesn't make much sense, you can acheive that with using a regular 1-pixel line with a 50% scaled canvas (but again it's a 1-pixel rendition, read below). See this snippet:
var canvas = document.querySelector('canvas');var context = canvas.getContext('2d');
function scale() { context.scale(0.5, 0.5); draw();}
function draw() { context.beginPath(); context.moveTo(100, 150); context.lineTo(450, 50); context.stroke();}
draw()
<canvas width="400" height="150"></canvas><button onclick="scale()">Scale down</button>
Horizonal and vertical lines drawn on HTML5 Canvas are two pixels wide, why?
The issue is certainly caused by the CSS box-sizing: border-box
rule.
When you set this rule, the CSS width
and height
values will also take the border-width in that size. This means that if e.g you have a 125x125px bitmap canvas, that you do resize to 500x500px through CSS with a 1px border, your bitmap will actually be stretched to (500-2) x (500-2)px. And scaling from 125 to 498 will not correspond to the scale of your monitor's DPI, hence there will be antialiasing when rendering your bitmap. This is probably even more noticeable with a x1 monitor since there you'll always go from original-size to original-size - 2px, but even on a x2 monitor you may face that issue.
So remove that rule and you should be fine.
Related Topics
How to Apply Same Content to Multiple HTML Pages
How to Use Text-Overflow Ellipsis in an HTML Input Field
Viewport Meta Tags Vs Media Queries
A Regular Expression to Remove a Given (X)HTML Tag from a String
Chrome Not Respecting Rem Font Size on Body Tag
Does CSS Grid Have a Flex-Grow Function
Styling The Placeholder in a Textfield
How to Alternate HTML Table Row Colors Using Jsp
Aligning Elements Left, Center and Right in Flexbox
Inner Div with Square Ratio and Flexbox
Fixed Positioning/Z-Index Issue in Mobile Safari
Playing Audio After The Page Loads in HTML
Is It Really Impossible to Make a Div Fit Its Size to Its Content
How to Get Horizontal Scroll Bar in Select Box in Ie