Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is a canvas drawn with an image a slightly different color than the image itself?

I am painting an image to a HTML canvas and getting image data from it to find the colors of specific pixels. The image is a map where each country is a different color. I want to cross reference the color returned by .getImageData() to a list of colors I made by hand to find which country any given pixel is in.

My issue is, the image I paint to the canvas and the image that is painted are slightly different colors.

This is the original picture being drawn to the canvas:

original picture

This is the picture I get when I download the canvas as a PNG:

Outcome

The two look almost identical, but if you download them and open them in a photo editing software, you will find they aren't. For example, the grey ocean in the top picture is 73,73,73,255 and the grey ocean in the bottom picture is 71,71,71,255.

I can still accomplish my goal of determining the country a pixel is in, but I need to make a new list based on the picture I download after it is painted to the canvas.

This just seems like a really weird issue.

If anyone is wondering, my code looks like this:

HTML:

<img scr="active_map.png" class="active" id="activeImage">
<canvas id="activeCanvas" width="439px" height="245px">Your Browser Doesn't Support HTML5 Canvases... Sorry :( </canvas>

JS:

var activeCanvas = document.getElementById('activeCanvas')
var activeContext = activeCanvas.getContext("2d");
var activeImage = document.getElementById('activeImage')
activeContext.drawImage(activeImage,0,0);
var activePixelData = activeContext.getImageData(0,0,439,245)["data"];

Also, I am using Firefox 31 for Ubuntu

-Thanks for the help

like image 258
obventio56 Avatar asked Jan 04 '15 16:01

obventio56


2 Answers

As @GameAlchemist mentions, there exists a potential gamma correction issue with the PNG you're using, namely that RGB values won't be interpreted consistently.

Henri Sivonen's The Sad Story of PNG Gamma “Correction” provides a very thorough analysis. In a nutshell:

In order to compensate for the different gammas, the display gamma of the system an image was authored on and the display gamma of the system where the image is being displayed both need to be known to the piece of software displaying the image. The PNG format provides a means for storing the (reciprocal of) display gamma of the authoring system to the image file. It is up to the program writing the file to store the native display gamma of the image, and then it is up to program displaying the file to compensate.

He recommends using Pngcrush to remove the color profiling that can cause this inconsistency. I've confirmed his technique (pngcrush -rem gAMA -rem cHRM -rem iCCP -rem sRGB infile.png outfile.png) does work. Here's your modified image:

enter image description here

You'll notice it is 6KB smaller. I've confirmed this works as expected before and after in Firefox (from RGB values of 71, 71, 71 to values of 73, 73, 73).

like image 89
Jacob Budin Avatar answered Nov 16 '22 04:11

Jacob Budin


Browsers have the freedom to premultiply alpha and (as GameAlchemist says) do gamma correction.

The result is that different browsers might display slightly different color values from the ones FF is displaying.

Possible workarounds:

  • Assume the browser will mess with your exact color, so have your test succeed on a small range of colors around the country's original color.

  • Send a pre-calculated array to the client representing each pixel on the canvas containing which country is under that pixel. This array would be similar to .getImageData, but with only 1 value per pixel--the value would be the country code for that pixel.

  • Calculate paths that outline each country. Then you can use context.isPointInPath to hit-test if the mouse is inside a country. DougX has done this nicely: http://dougx.net/map/

  • If memory is a concern, you can refactor the pre-calculated array above to use objects in a tree structure. For example:

    1. Divide your map into a 9x4 grid (or 4x2 or whatever).
    2. Create an array of objects for each grid-cell. The objects might look like this: { x0:297, x1:353 y0:91, y1:94 country:55 } where countryCodes[55]='China'. This example object represents (353-297)*(94-91)==224 pixels.
    3. On mouseclick, calculate which grid-cell the mouse is in and then scan the array until you find the exact mouse [x,y]. You can omit all ocean coordinates from the grid arrays so if your test doesn't find a match, you can assume the mouse is in the ocean.
like image 26
markE Avatar answered Nov 16 '22 06:11

markE