Must check all pixels
To find a pixel that matches a color will require, in the worst case (pixel of that color not in image), that you step over every pixel in the image.
How not to do it
Converting every pixel to a DOM string is about the worst way to do it, as DOM string use a lot of memory and CPU overhead, especially if instantiated using jQuery (which has its own additional baggage)
Hex color to array
To find the pixel you need only check each pixels color data against the HEX value. You convert the hex value to an array of 3 Bytes.
The following function will convert from CSS Hex formats "#HHH"
"#HHHH"
, "#HHHHHH"
and "#HHHHHHHH"
ignoring the alpha part if included, to an array of integers 0-255
const hex2RGB = h => {
if(h.length === 4 || h.length === 5) {
return [parseInt(h[1] + h[1], 16), parseInt(h[2] + h[2], 16), parseInt(h[3] + h[3], 16)];
}
return [parseInt(h[1] + h[2], 16), parseInt(h[3] + h[4], 16), parseInt(h[5] + h[6], 16)];
}
Finding the pixel
I do not know how you plan to use such a feature so the example below is a general purpose method that will help and can be modified as needed
It will always find a pixel if you let it even if there is no perfect match. It does this by finding the closest color to the color you are looking for.
The reason that of finds the closest match is that when you draw an image onto a 2D canvas the pixel values are modified slightly if the image has transparent pixels (pre-multiplied alpha)
The function finds the pixel by measuring the spacial distance between the pixel and the hex color (simple geometry Pythagoras). The closest color is the one that is the smallest distance.
It will return the object
{
x, // the x coordinate of the match
y, // the y coordinate of the match
distance, // how closely the color matches the requested color.
// 0 means a perfect match
// to 441 completely different eg black and white
// value is floored to an integer value
}
If the image is tainted (cross origin, local device storage), or you pass something that can not be converted to pixels the function will return undefined
The function keeps a canvas that it uses to get pixel data as it assumes that it will be use many times. If the image is tainted it will catch the error (add a warning to the console), cleanup the tainted canvas and be ready for another image.
Usage
To use the function add it to your code base, it will setup automatically.
Get an image and a hex value and call the function with the image
, CSS hex
color, and optionally the threshold distance for the color match.
Eg find exact match for #FF0000
const result = findPixel(origImage, "#FF0000", 0); // find exact match for red
if (result) { // only if found
console.log("Found color #FF0000 at pixel " + result.x + ", " + result.y);
} else {
console.log("The color #FF0000 is not in the image");
}
or find color close to
const result = findPixel(origImage, "#FF0000", 20); // find a match for red
// within 20 units.
// A unit is 1 of 256
if (result) { // only if found
console.log("Found closest color within " + result.distance + "units of #FF0000 at pixel " + result.x + ", " + result.y);
}
or find closest
// find the closest, no threshold ensures a result
const result = findPixel(origImage, "#FF0000");
console.log("Found closest color within " + result.distance + "units of #FF0000 at pixel " + result.x + ", " + result.y);
Code
The function is as follows.
const findPixel = (() => {
var can, ctx;
function createCanvas(w, h) {
if (can === undefined){
can = document.createElement("canvas");
ctx = can.getContext("2d");
}
can.width = w;
can.height = h;
}
function getPixels(img) {
const w = img.naturalWidth || img.width, h = img.naturalHeight || img.height;
createCanvas(w, h);
ctx.drawImage(img, 0, 0);
try {
const imgData = ctx.getImageData(0, 0, w, h);
can.width = can.height = 1; // make canvas as small as possible so it wont
// hold memory. Leave in place to avoid instantiation overheads
return imgData;
} catch(e) {
console.warn("Image is un-trusted and pixel access is blocked");
ctx = can = undefined; // canvas and context can no longer be used so dump them
}
return {width: 0, height: 0, data: []}; // return empty pixel data
}
const hex2RGB = h => { // Hex color to array of 3 values
if(h.length === 4 || h.length === 5) {
return [parseInt(h[1] + h[1], 16), parseInt(h[2] + h[2], 16), parseInt(h[3] + h[3], 16)];
}
return [parseInt(h[1] + h[2], 16), parseInt(h[3] + h[4], 16), parseInt(h[5] + h[6], 16)];
}
const idx2Coord = (idx, w) => ({x: idx % w, y: idx / w | 0});
return function (img, hex, minDist = Infinity) {
const [r, g, b] = hex2RGB(hex);
const {width, height, data} = getPixels(img);
var idx = 0, found;
while (idx < data.length) {
const R = data[idx] - r;
const G = data[idx + 1] - g;
const B = data[idx + 2] - b;
const d = R * R + G * G + B * B;
if (d === 0) { // found exact match
return {...idx2Coord(idx / 4, width), distance: 0};
}
if (d < minDist) {
minDist = d;
found = idx;
}
idx += 4;
}
return found ? {...idx2Coord(found / 4, width), distance: minDist ** 0.5 | 0 } : undefined;
}
})();
This function has been tested and works as described above.
Note Going by the code in the your question the alpha value of the image and CSS hex color is ignored.
Note that if you intend to find many colors from the same image this function is not the best suited for you needs. If this is the case let me know in the comment and I can make changes or instruct you how to optimism the code for such uses.
Note It is not well suited for single use only. However if this is the case change the line const findPixel = (() => {
to var findPixel = (() => {
and after you have used it remove the reference findpixel = undefined;
and JS will clean up any resources it holds.
Note If you also want to get the actual color of the closest found color that is trivial to add as well. Ask in the comments.
Note It is reasonably quick (you will be hard pressed to get a quicker result) but be warned that for very large images 4K and above it may take a bit, and on very low end devices it may cause a out of memory error. If this is a problem then another solution is possible but is far slower.