Get a Pixel from HTML Canvas

Get a pixel from HTML Canvas?

There's a section about pixel manipulation in the W3C documentation.

Here's an example on how to invert an image:

var context = document.getElementById('myCanvas').getContext('2d');

// Get the CanvasPixelArray from the given coordinates and dimensions.
var imgd = context.getImageData(x, y, width, height);
var pix = imgd.data;

// Loop over each pixel and invert the color.
for (var i = 0, n = pix.length; i < n; i += 4) {
pix[i ] = 255 - pix[i ]; // red
pix[i+1] = 255 - pix[i+1]; // green
pix[i+2] = 255 - pix[i+2]; // blue
// i+3 is alpha (the fourth element)
}

// Draw the ImageData at the given (x,y) coordinates.
context.putImageData(imgd, x, y);

Get pixels' colors from HTML Canvas WITHOUT getImageData()?

There is. Since you're running from an extension your extension will have privileged access to cross-origin sources but only if loaded via fetch() and XMLHttpRequest() from a content script (or background script) - excerpt from that link:

Content scripts get the same cross-domain privileges as the rest of
the extension: so if the extension has requested cross-domain access
for a domain using the permissions key in manifest.json, then its
content scripts get access that domain as well.

This is accomplished by exposing more privileged XHR and fetch
instances in the content script [...]

Please note that these calls when called from a content script will not set origin and referer headers which sometimes can cause problems if the cross-origin site expects these to be set - for those cases you will need to use the non-privileged content.XMLHttpRequest or content.fetch() which will bring you back to square one.

The permissions in the manifest file (or if set permissions dynamically) must also allow access to these cross-origin sites.

This means however that you will have to "reload" the image source separately via these calls. You can do this the following way by first obtaining the original URL to the image you want to load, say, from a content script:

// example loading all images in current tab
let images = document.querySelectorAll("img");
for(let image of images) loadAsBitmap(image.src); // some sub-call using the url

Then load that source via the content script's fetch():

fetch(src).then(resp => {  // load from original source
return resp.blob(); // obtain a blob
}).then(blob => { // convert blob, see below
// ...
};

When the blob is obtained you can convert it to an Object-URL and set that as source for an image and be able to go around the cross-origin restriction we otherwise face. In the content script, next steps would be:

let url = URL.createObjectURL(blob);         // attach Object-URL to blob
let img = new Image(); // create image element *
img.onload = () => { // attach handler
let c = document.createElement("canvas"); // create canvas
let ctx = c.getContext("2d"); // get context
c.width = img.width; // canvas size = image
c.height = img.height;
ctx.drawImage(img, 0, 0); // draw in image
URL.revokeObjectURL(url); // remove reference.
let imageData =
ctx.getImageData(0,0,c.width,c.height); // get image data
// .. callback to a function that handles the image data
};
img.src = url; // start loading blob

Get pixel color in canvas at position in console

Canvas elements can have only one Rendering Context attached to them. Every time you call getContext(type) after the first initialization, it will return either the same context object if you used the same type parameter, either null if you used an other context's type.

From your markup, it sounds that your canvas has an webgl context attached to it.

So when you call getContext('2d'), it will return null.

Here is an example showing you how to use it from an context you didn't directly initialized.
The webgl's getImageData equivalent method is readPixels().

// we have access to the DOM element
var canvas = document.querySelector('canvas');
// we need to get the correct context type, or it will return null
var gl = canvas.getContext('webgl') || canvas.getContext('webgl2');
// where we'll store our pixels info
var pixels = new Uint8Array(4);

canvas.addEventListener('click', function(e) {
var x = e.clientX - canvas.offsetLeft;
var y = e.clientY - canvas.offsetTop;
// we need to call it in the same execution flow as 'render' because webgl erase the drawing buffer by default
// this can be done by stacking our code in the next frame.
requestAnimationFrame(function() {
gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
console.log(pixels);
});
});

// we have access to the DOM elementvar canvas = document.querySelector('canvas');// we need to get the correct context type, or it will return nullvar gl = canvas.getContext('webgl') || canvas.getContext('webgl2');// where we'll store our pixels infovar pixels = new Uint8Array(4);
canvas.addEventListener('click', function(e) { var x = e.clientX - canvas.offsetLeft; var y = e.clientY - canvas.offsetTop; // we need to call it in the same execution flow as 'render' because webgl erase the drawing buffer by default // this can be done by stacking our code in the next frame. requestAnimationFrame(function() { gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels); console.log(pixels); });});
<!-- Example of external code, on which we don't have direct access.Taken from https://github.com/mrdoob/three.js/blob/master/examples/webgl_geometry_cube.html-->
<base href="https://threejs.org/examples/"><script src="../build/three.js"></script><script>(function(){ var camera, scene, renderer; var mesh; init(); animate(); console.clear();
function init() { camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000); camera.position.z = 400; scene = new THREE.Scene(); var geometry = new THREE.BoxBufferGeometry(200, 200, 200); var material = new THREE.MeshBasicMaterial(); mesh = new THREE.Mesh(geometry, material); scene.add(mesh); renderer = new THREE.WebGLRenderer(); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // window.addEventListener('resize', onWindowResize, false); }
function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }
function animate() { requestAnimationFrame(animate); mesh.rotation.x += 0.005; mesh.rotation.y += 0.01; renderer.render(scene, camera); }})();</script>

What's the best way to set a single pixel in an HTML5 canvas?

There are two best contenders:

  1. 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 );
  2. 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 reader
  • creating 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.

Get pixel color from canvas, on mousemove

Here's a complete, self-contained example. First, use the following HTML:

<canvas id="example" width="200" height="60"></canvas>
<div id="status"></div>

Then put some squares on the canvas with random background colors:

var example = document.getElementById('example');
var context = example.getContext('2d');
context.fillStyle = randomColor();
context.fillRect(0, 0, 50, 50);
context.fillStyle = randomColor();
context.fillRect(55, 0, 50, 50);
context.fillStyle = randomColor();
context.fillRect(110, 0, 50, 50);

And print each color on mouseover:

$('#example').mousemove(function(e) {
var pos = findPos(this);
var x = e.pageX - pos.x;
var y = e.pageY - pos.y;
var coord = "x=" + x + ", y=" + y;
var c = this.getContext('2d');
var p = c.getImageData(x, y, 1, 1).data;
var hex = "#" + ("000000" + rgbToHex(p[0], p[1], p[2])).slice(-6);
$('#status').html(coord + "<br>" + hex);
});

The code above assumes the presence of jQuery and the following utility functions:

function findPos(obj) {
var curleft = 0, curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
return { x: curleft, y: curtop };
}
return undefined;
}

function rgbToHex(r, g, b) {
if (r > 255 || g > 255 || b > 255)
throw "Invalid color component";
return ((r << 16) | (g << 8) | b).toString(16);
}

function randomInt(max) {
return Math.floor(Math.random() * max);
}

function randomColor() {
return `rgb(${randomInt(256)}, ${randomInt(256)}, ${randomInt(256)})`
}

See it in action here:

  • https://bl.ocks.org/wayneburkett/ca41a5245a9f48766b7bc881448f9203

// set up some sample squares with random colors
var example = document.getElementById('example');
var context = example.getContext('2d');
context.fillStyle = randomColor();
context.fillRect(0, 0, 50, 50);
context.fillStyle = randomColor();
context.fillRect(55, 0, 50, 50);
context.fillStyle = randomColor();
context.fillRect(110, 0, 50, 50);

$('#example').mousemove(function(e) {
var pos = findPos(this);
var x = e.pageX - pos.x;
var y = e.pageY - pos.y;
var coord = "x=" + x + ", y=" + y;
var c = this.getContext('2d');
var p = c.getImageData(x, y, 1, 1).data;
var hex = "#" + ("000000" + rgbToHex(p[0], p[1], p[2])).slice(-6);
$('#status').html(coord + "<br>" + hex);
});

function findPos(obj) {
var curleft = 0, curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
return { x: curleft, y: curtop };
}
return undefined;
}

function rgbToHex(r, g, b) {
if (r > 255 || g > 255 || b > 255)
throw "Invalid color component";
return ((r << 16) | (g << 8) | b).toString(16);
}

function randomInt(max) {
return Math.floor(Math.random() * max);
}

function randomColor() {
return `rgb(${randomInt(256)}, ${randomInt(256)}, ${randomInt(256)})`
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="example" width="200" height="60"></canvas>
<div id="status"></div>

HTML 5 Canvas - Get pixel data for a path

Step 1: Draw the paths to a second canvas of the same size as you original, let's call it the 'path canvas'.

Step 2: Set the globalCompositeOperation of the path canvas to 'destination-in'.

Step 3: draw you original canvas onto the path canvas

Step 4: Loop through all the pixels of the path canvas and store the pixels that are not transparent in whatever format you're sending them to the server.

How to get the Pixel data from the Canvas

You need to print Alpha channel to console as well. I also received 0,0,0 for all pixels. Since default canvas's background is (0,0,0,0) which is transparent. And black font is (0,0,0,noneZeroValue): (could be not 255 because of antialising). If you won't print alpha channel all of them will be 0,0,0.

<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="100" height="20" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>

<script>
window.onload = function() {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
//ctx.font = "48px serif";
ctx.fillText("A lot of text", 0, 10);
//ctx.drawImage(ocanvas,0,0,c.width,c.height);
var imageData = ctx.getImageData(0, 0, c.width, c.height);
var px = imageData.data;
var len = px.length;

for (var i = 0; i < len; i+=4) {
var redPx = px[i];
var greenPx = px[i+1];
var bluePx = px[i+2];
var alphaPx = px[i+3];
//if (redPx!=0 || greenPx!=0 ||bluePx!=0 || alphaPx!=0){
console.log(redPx,greenPx,bluePx,alphaPx);


}

};


</script>

</body>
</html>


Related Topics



Leave a reply



Submit