144

Is there any way to check if a selected (x,y) point of a PNG image is transparent?

ashleedawg
  • 20,365
  • 9
  • 72
  • 105
Danny Fox
  • 38,659
  • 28
  • 68
  • 94

6 Answers6

226

Building on Jeff's answer, your first step would be to create a canvas representation of your PNG. The following creates an off-screen canvas that is the same width and height as your image and has the image drawn on it.

var img = document.getElementById('my-image');
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);

After that, when a user clicks, use event.offsetX and event.offsetY to get the position. This can then be used to acquire the pixel:

var pixelData = canvas.getContext('2d').getImageData(event.offsetX, event.offsetY, 1, 1).data;

Because you are only grabbing one pixel, pixelData is a four entry array containing the pixel's R, G, B, and A values. For alpha, anything less than 255 represents some level of transparency with 0 being fully transparent.

Here is a jsFiddle example: http://jsfiddle.net/thirtydot/9SEMf/869/ I used jQuery for convenience in all of this, but it is by no means required.

Note: getImageData falls under the browser's same-origin policy to prevent data leaks, meaning this technique will fail if you dirty the canvas with an image from another domain or (I believe, but some browsers may have solved this) SVG from any domain. This protects against cases where a site serves up a custom image asset for a logged in user and an attacker wants to read the image to get information. You can solve the problem by either serving the image from the same server or implementing Cross-origin resource sharing.

Brian Nickel
  • 26,890
  • 5
  • 80
  • 110
  • For this to be correct, you need to set the coordinate space of the canvas i.e. set the width and height to match the image dimensions. CSS width and height only serve to stretch the final canvas for layout, which doesn't help when it's an undisplayed element. Try something like $('');. Or does this somehow not apply with off-screen canvases? – fabspro Dec 12 '12 at 14:13
  • @fabspro: That's an excellent point. Since drawing and reading the image data are done via the context, the width and height values are meaningless. They don't have any ill effect because the canvas not the context is contorted and the reason I didn't see this effect on my demo is simply because the image is smaller than the context's initial width/height. I will update accordingly, though it is safer to access `.width` and `.height` on the canvas itself than through concatenation. – Brian Nickel Dec 14 '12 at 21:20
  • @BrianNickel Thanks for the useful information. I guess my current understanding is that the width and height properties of the canvas element set the size of the context (and the area you can draw), and presumably anything drawn outside of that region would do nothing. Then the context (size defined by width and height properties) is stretched like a regular image to fit the css size if it differs from the context size. – fabspro Dec 15 '12 at 05:16
  • 4
    Fix it for Firefox http://jsfiddle.net/9SEMf/622/ using this http://stackoverflow.com/questions/11334452/event-offsetx-in-firefox – benoît Sep 23 '13 at 12:02
  • The example provided in Fiddle is not updated accordingly and isn't working. One should add this.canvas.width = img.width and same with height. – wilkas Oct 28 '13 at 13:55
  • 8
    You can't use this for remote images. "Unable to get image data from canvas because the canvas has been tainted by cross-origin data. SecurityError: An attempt was made to break through the security policy of the user agent." – Karl Glaser Nov 15 '13 at 19:36
  • 1
    Another point to be careful of: pixel values rendered with drawImage may be different from values in your image because of color space correction. Good thing that this only happens if image contains color space information. – ironic Aug 13 '14 at 08:06
  • +benoît: When I use your Firefox fix it puts the offset in the wrong place if I'm scrolled down the page – Mala Feb 19 '15 at 01:06
  • Note that you probably want `event.x` and `event.y`, otherwise this will break if the canvas is scaled. – Brad Jan 21 '17 at 19:44
  • I guess you only need to load that one pixel to the canvas – Kári Gunnarsson Jul 20 '21 at 10:29
21

Canvas would be a great way to do this, as @pst said above. Check out this answer for a good example:

getPixel from HTML Canvas?

Some code that would serve you specifically as well:

var imgd = context.getImageData(x, y, width, height);
var pix = imgd.data;

for (var i = 0, n = pix.length; i < n; i += 4) {
  console.log pix[i+3]
}

This will go row by row, so you'd need to convert that into an x,y and either convert the for loop to a direct check or run a conditional inside.

Reading your question again, it looks like you want to be able to get the point that the person clicks on. This can be done pretty easily with jquery's click event. Just run the above code inside a click handler as such:

$('el').click(function(e){
   console.log(e.clientX, e.clientY)
}

Those should grab your x and y values.

Community
  • 1
  • 1
Jeff Escalante
  • 3,137
  • 1
  • 21
  • 30
  • This isn't what the commenter asked (seemingly), and doesn't answer the question at all. The accepted answer does work. I recommend deleting this answer... – Agamemnus Jan 13 '19 at 21:44
7

The two previous answers demonstrate how to use Canvas and ImageData. I would like to propose an answer with runnable example and using an image processing framework, so you don't need to handle the pixel data manually.

MarvinJ provides the method image.getAlphaComponent(x,y) which simply returns the transparency value for the pixel in x,y coordinate. If this value is 0, pixel is totally transparent, values between 1 and 254 are transparency levels, finally 255 is opaque.

For demonstrating I've used the image below (300x300) with transparent background and two pixels at coordinates (0,0) and (150,150).

enter image description here

Console output:

(0,0): TRANSPARENT
(150,150): NOT_TRANSPARENT

image = new MarvinImage();
image.load("https://i.imgur.com/eLZVbQG.png", imageLoaded);

function imageLoaded(){
  console.log("(0,0): "+(image.getAlphaComponent(0,0) > 0 ? "NOT_TRANSPARENT" : "TRANSPARENT"));
  console.log("(150,150): "+(image.getAlphaComponent(150,150) > 0 ? "NOT_TRANSPARENT" : "TRANSPARENT"));
}
<script src="https://www.marvinj.org/releases/marvinj-0.7.js"></script>
Gabriel Archanjo
  • 4,547
  • 2
  • 34
  • 41
7

Building on Brian Nickel's answer, only the wanted single pixel of the source image is drawn onto a 1*1 pixel canvas, which is more efficient than drawing the entire image just to get a single pixel:

function getPixel(img, x, y) {

    let canvas = document.createElement('canvas');
    canvas.width = 1;
    canvas.height = 1;
    canvas.getContext('2d').drawImage(img, x, y, 1, 1, 0, 0, 1, 1);;
    let pixelData = canvas.getContext('2d').getImageData(0, 0, 1, 1).data;

    return pixelData;   
}
nik
  • 543
  • 5
  • 7
2

With : i << 2

const pixels = context.getImageData(x, y, width, height).data;

for (let i = 0, dx = 0; dx < data.length; i++, dx = i << 2) 
{
    if (pixels[dx+3] <= 8) { console.log("transparent x= " + i); }
}
user2226755
  • 12,494
  • 5
  • 50
  • 73
0

Here's a consolidation of a few answers into a runnable snippet that lets you upload a file, hover to preview the RGB value of each pixel, then click to put the RGB in a div.

Pertinent to the original question, the last value (alpha) is the transparency. 0 is fully transparent and 255 is fully opaque.

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const input = document
  .querySelector('input[type="file"]');
input.addEventListener("change", e => {
  const image = new Image();
  image.addEventListener("load", e => {
    const {width, height} = image;
    canvas.width = width;
    canvas.height = height;
    ctx.drawImage(image, 0, 0);
    const {data} = ctx.getImageData(
      0, 0, width, height
    );
    const rgb = (x, y) => {
      const i = (x + y * width) * 4;
      return data.slice(i, i + 4).join(", ");
    };
    canvas.addEventListener("mousemove", event => {
      const {offsetX: x, offsetY: y} = event;
      console.log(rgb(x, y));
    });
    canvas.addEventListener("click", event => {
      const {offsetX: x, offsetY: y} = event;
      document.querySelector("div")
        .textContent = rgb(x, y);
    });
  });
  image.addEventListener("error", () =>
    console.error("failed")
  );
  image.src = URL
    .createObjectURL(event.target.files[0]);
});
.as-console-wrapper {
  height: 21px !important;
}
<div>
  Upload image and mouseover to preview RGB. Click to select a value.
</div>
<form>
  <input type="file">
</form>
<canvas></canvas>

References:

ggorlen
  • 44,755
  • 7
  • 76
  • 106