Find specific color passage / spectra in image - javascript

I have an image like the example below...
---> RESULT: #ffdf00 => something like 30%
... and I'd like to find the occurrence (percentage) of a specific color in the image (e.g. #ffdf00 - yellow), or even better I'd like to find the occurrence (percentage) of a specific color passage (e.g #ffdf00 ---too---> #dabf09) with simple javascript.
I've been already looking for javascript library's but all I found till' now is extracting the main color of the image like the plugins below:
getImageColor
VibrantJS
However I'm sure something like color thief can do the job, but I have no clue how to "SEARCH FOR SPECIFIC COLOR SPECTRA AND GET THE PERCENTAGE OCCURRENCE" (#ffdf00 ---too---> #dabf09), so I hope somebody can help me with my problem.
However, this is all what I got so far & now I've no clue how to continue...
function draw(img) {
var canvas = document.createElement("canvas");
var c = canvas.getContext('2d');
c.width = canvas.width = img.width;
c.height = canvas.height = img.height;
c.clearRect(0, 0, c.width, c.height);
c.drawImage(img, 0, 0, img.width , img.height);
return c;
}
function getColors(c) {
var col, colors = {};
var pixels, r, g, b, a;
r = g = b = a = 0;
pixels = c.getImageData(0, 0, c.width, c.height);
for (var i = 0, data = pixels.data; i < data.length; i += 4) {
r = data[i];
g = data[i + 1];
b = data[i + 2];
a = data[i + 3];
if (a < (255 / 2))
continue;
col = rgbToHex(r, g, b);
if (!colors[col])
colors[col] = 0;
colors[col]++;
}
return colors;
}
function rgbToHex(r, g, b) {
return ((r << 16) | (g << 8) | b).toString(16);
}
function pad(hex) {
return ("000000" + hex).slice(-6);
}
var img=document.getElementById('img')
var colors = getColors(draw(img));
<img width=200 id='img' src='https://s.aolcdn.com/hss/storage/midas/b386937631a1f03665c1d57289070898/203417456/simpsons.jpg'>
Thanks a million in advance, jonas

Related

Extract 'dominant' colors from HSV model? (not from RGB model)

i'm looking for a get color pallet from image.
i could get RGB data from image
getRgbData() {
this.canvas = window.document.createElement('canvas')
this.context = this.canvas.getContext('2d')
this.width = this.canvas.width = image.width || image.naturalWidth
this.height = this.canvas.height = image.height || image.naturalHeight
this.context.drawImage(image, 0, 0, this.width, this.height)
return this.context.getImageData(0, 0, this.width, this.height)
}
and convert RGB values to HSV model (rgbToHsv method wrote from https://gist.github.com/mjackson/5311256#file-color-conversion-algorithms-js-L1)
getHsvData() {
const { data, width, height } = this.getRgbData()
const pixcel = width * height
const q = 1
const array = []
for (var i = 0, r, g, b, offset; i < pixcel; i = i + q) {
offset = i * 4
r = data[offset + 0]
g = data[offset + 1]
b = data[offset + 2]
array.push({ r, g, b })
}
return array.map(l => this.rgbToHsv(l.r, l.g, l.b))
}
it result like this (it is converted data from RGB 24bit)
[
{h: 0.6862745098039215, s: 0.7727272727272727, v: 0.17254901960784313},
{h: 0.676470588235294, s: 0.723404255319149, v: 0.1843137254901961},
.....
]
color-thief and vibrant.js is get dominant color from RGB model, but i want to
get dominant color from converted HSV model.
(i heard that extract color from hsv is more fit in human eyes. is it right?)
how can i extract color form HSV model..?
First thing we need to do is get the average color of the image. We can do that by adding each color channel individually then dividing by the height and the width of the canvas.
function channelAverages(data, width, height) {
let r = 0, g = 0, b = 0
let totalPixels = width * height
for (let i = 0, l = data.data.length; i < l; i += 4) {
r += data.data[i]
g += data.data[i + 1]
b += data.data[i + 2]
}
return {
r: Math.floor(r / totalPixels),
g: Math.floor(g / totalPixels),
b: Math.floor(b / totalPixels)
}
}
Next we will want to convert the returned color's average to HSL, we can do that with this function (Which you also link to above).
function rgbToHsl(r, g, b) {
r /= 255, g /= 255, b /= 255;
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, l = (max + min) / 2;
if (max == min) {
h = s = 0; // achromatic
} else {
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [h, s, l];
}
So, to get our output we can do this:
let data = ctx.getImageData(0, 0, canvas.width, canvas.height)
let avg = channelAverages(data, width, height)
console.log(rgbToHsl(avg.r, avg.g, avg.b))
If we want numbers we can use in an editor (Such as PhotoShop or Gimp) to verify our results, we just need to multiply each:
h = h * 360
Example: 0.08 * 360 = 28.8
s = s * 100
Example: 0.85 * 100 = 85
l = l * 100
Example: 0.32 * 100 = 32
There is a library called Kleur.js you can use to get images but remember that it gives random color palette every time. But the dominant color will remain the same in every color pallete
// Create the Kleur Object
Kleur = new Kleur();
// Set the image link to get the palette from
imageObj = Kleur.init(imgLink);
// Wait for the image to load
imageObj.onload = function(e) {
// get the color array from the image
let colorArr = Kleur.getPixelArray(imageObj);
// pass the array to generate the color array
let array_of_pixels = Kleur.generateColorArray(colorArr);
// you can get the dominant color from the image
const dominant = Kleur.getDominant(array_of_pixels);
// log the light colors and the dominant color
console.log(light, dominant)
}
if you want to see an example for using this code visit the codepen
And if you want all the dominant colors which I think is the colors with the most pixels you can access the array_of_pixels, so you could do
// for first five dominant color
for(let x = 0; x < 5; x++){
console.log(array_of_pixels[x].hsv);
}
// for the dominant colors hsv value
console.log(dominant.hsv)
this will log the hsv values for the five most dominant color in the image(usually the dominant colors are really similar so look out for that)
Kleur js returns colors in various color space
RGB
HEX
HSV
XYZ
LAB
LCH
it also returns count which is the number of pixels that have the color

Best RGB combination to convert image into Black and White "threshold"

I am required to build a simple application that converts a color image or a grayscale image into a black and white one, I was thinking looping through each pixel and checking the RGB values and if all of them are less than an specific value (lets say 20) redraw the pixel to black and if it is greater than that value, redraw the pixel to white. Something like this.
function blackWhite(context, canvas) {
var imgData = context.getImageData(0, 0, canvas.width, canvas.height);
var pixels = imgData.data;
for (var i = 0, n = pixels.length; i < n; i += 4) {
if (pixels[i] <= 20 || pixels[i+1] <= 20 || pixels[i+2] <= 20){
pixels[i ] = 0; // red
pixels[i+1] = 0; // green
pixels[i+2] = 0; // blue
}else{
pixels[i ] = 255; // red
pixels[i+1] = 255; // green
pixels[i+2] = 255; // blue
}
}
//redraw the image in black & white
context.putImageData(imgData, 0, 0);
}
The big question is, what is the correct combination of Red, green and blue to define a pixel to be black, this taking into account that colors are perceived different by the human eye, as an example for our eyes it is more important the green color than the red and the blue, I’ve tried experimentally some values, but I don’t get close to a black and with image like the one you could get by digitalizing a sheet in an scanner as black and white.
Of course if there is a much faster way to do this, I will totally appreciate it.
I believe what you're looking for is the relative luminance. While not the most advanced method of thresholding, it better follows the way humans perceive light, which is what I think you want.
https://en.wikipedia.org/wiki/Relative_luminance
From the wikipedia article the luminance can be calculated as follows:
let lum = .2126 * red + .7152 * green + .0722 * blue
This value will be some fraction of one so if you want to split it right in the middle use a threshold of .5
EDIT
The real issue comes with selecting the threshold. Not all images are lit the same way and a picture with more pixels with a low luminosity (i.e., more blacks) will benefit from a lower threshold.
There's a couple techniques you can consider using such as analyzing the histogram of the image.
(I'm adding this for future visitors.) For converting images to black & white where the characteristics such as luma dominance, gamma etc. are unknown, the "Otsu's method" tends to provide good results.
It's a fairly simple algorithm which uses a luma histogram of the image combined with pixel count to find an optimal cluster-based threshold value.
The main steps are (source: ibid):
Building a Histogram
So the first thing we need to do is to build a histogram. This will require converting RGB to luminance using either a flat 33.3% factor or as in Khauri's answer a Rec.709 (for HD) formula (Rec. 601 can be used as well). Note that Rec.* factors assumes that the RGB is converted to linear format; modern browsers will typically apply gamma (non-linear) to the image used for canvas. But lets ignore that here.
A flat conversion can be beneficial performance wise but provides less accurate result:
var luma = Math.round((r + g + b) * 0.3333);
while Rec.709 will give a better result (with linear data):
var luma = Math.round(r * 0.2126 + g * 0.7152 + b * 0.0722);
So, convert each pixel to integer luma value, use the resulting value as index in a 256 large array and increment for the index:
var data = ctx.getImageData(0, 0, width, height).data;
var histogram = new Uint16Array(256); // assuming smaller images here, ow: Uint32
// build the histogram using Rec. 709 for luma
for(var i = 0; i < data.length; i++) {
var luma = Math.round(data[i++] * 0.2126 + data[i++] * 0.7152 + data[i++] * 0.0722);
histogram[luma]++; // increment for this luma value
}
Find Optimal Cluster-based Threshold value
Now that we have a histogram we can feed that to the Oto's method and obtain a black & white version of the image.
Translated into JavaScript we would do (source for method part based on variant 2 from ibid):
// Otsu's method, from: https://en.wikipedia.org/wiki/Otsu%27s_Method#Variant_2
//
// The input argument pixelsNumber is the number of pixels in the given image. The
// input argument histogram is a 256-element histogram of a grayscale image
// different gray-levels.
// This function outputs the threshold for the image.
function otsu(histogram, pixelsNumber) {
var sum = 0, sumB = 0, wB = 0, wF = 0, mB, mF, max = 0, between, threshold = 0;
for (var i = 0; i < 256; i++) {
wB += histogram[i];
if (wB === 0) continue;
wF = pixelsNumber - wB;
if (wF === 0) break;
sumB += i * histogram[i];
mB = sumB / wB;
mF = (sum - sumB) / wF;
between = wB * wF * Math.pow(mB - mF, 2);
if (between > max) {
max = between;
threshold = i;
}
}
return threshold>>1;
}
// Build luma histogram
var c = document.createElement("canvas"),
ctx = c.getContext("2d"),
img = new Image();
img.crossOrigin = "";
img.onload = go;
img.src = "//i.imgur.com/tbRxrWA.jpg";
function go() {
c.width = this.width;
c.height = this.height;
ctx.drawImage(this, 0, 0);
var idata = ctx.getImageData(0, 0, c.width, c.height);
var data = idata.data;
var histogram = new Uint16Array(256);
// build the histogram using flat factors for RGB
for(var i = 0; i < data.length; i += 4) {
// note: here we also store luma to red-channel for reuse later.
var luma = data[i] = Math.round(data[i]*.2126+data[i+1]*.7152+data[i+2]*.0722);
histogram[luma]++;
}
// Get threshold
var threshold = otsu(histogram, c.width * c.height);
console.log("Threshold:", threshold);
// convert image
for(i = 0; i < data.length; i += 4) {
// remember we stored luma to red channel.. or use a separate array for luma values
data[i] = data[i+1] = data[i+2] = data[i] >= threshold ? 255 : 0;
}
// show result
ctx.putImageData(idata, 0, 0);
document.body.appendChild(c); // b&w version
document.body.appendChild(this); // original image below
}
Also see the improvements section.
UPDATE
I did not read the question correctly so I have updated the answer to reflect the question. Will leave old answer in just as a point of interest for those that are.
To create the simplest threshold filter just sum the RGB channels and if over the threshold value the make pixel white else black.
// assumes canvas and ctx defined;
// image to process, threshold level range 0 - 255
function twoTone(image, threshold) {
ctx.drawImage(image,0,0):
const imgD = ctx.getImageData(0, 0, canvas.width, canvas.height);
const d = imgD.data;
var v,i = 0;
while (i < d.length) {
v = (d[i++] + d[i++] + d[i]) < (threshold * 3) ? 0 : 255;
i -= 2;
d[i++] = d[i++] = d[i++] = v;
i++;
}
ctx.putImageData(imgD, 0, 0);
}
But there are other ways. A modification of the above lets you create a gradient at the threshold. This softens the hard boundary that the above method can produce.
Sometimes you need the function to be quick, or you may not be able to access the pixel data due to cross origin security restrictions. In that case you can use a stack composite operation approch that creates a threshold by layering the image in a succession of "multiply" and "lighter" globalCompositeOperations. Though this method can produce high quality results, the input values are a little fuzzy as presented in the example below. You would have to calibrate it if you wanted to match the specific threshold and cutoff width.
Demo
UPDATE
As there is more information in the form of answers i have updated the code to keep the comparisons fair.
I have updated the demo to include K3N image of the bear and have provided 3 methods for finding the threshold via a mean. (I have modified the code in K3N's answer to fit the demo. It is functionally the same). The Buttons at the top let you select from the two images and display size and the last three find and apply the threshold value using three methods.
Use the sliders to change the threshold and cutoff and amount values where applicable.
const image = new Image;
const imageSrcs = ["https://upload.wikimedia.org/wikipedia/en/2/24/Lenna.png", "//i.imgur.com/tbRxrWA.jpg"];
var scaleFull = false;
var imageBWA;
var imageBWB;
var imageBWC;
var amountA = -1;
var thresholdA = -1;
var thresholdB = -1;
var cutoffC = -1;
var thresholdC = -1;
start();
//Using stacked global composite operations.
function twoTone(bw, amount, threshold) {
bw.ctx.save();
bw.ctx.globalCompositeOperation = "saturation";
bw.ctx.fillStyle = "#888"; // no saturation
bw.ctx.fillRect(0, 0, bw.width, bw.height);
amount /= 16;
threshold = 255 - threshold;
while (amount-- > 0) {
bw.ctx.globalAlpha = 1;
bw.ctx.globalCompositeOperation = "multiply";
bw.ctx.drawImage(bw, 0, 0);
const a = (threshold / 127);
bw.ctx.globalAlpha = a > 1 ? 1 : a;
bw.ctx.globalCompositeOperation = "lighter";
bw.ctx.drawImage(bw, 0, 0);
if (a > 1) {
bw.ctx.globalAlpha = a - 1 > 1 ? 1 : a - 1;
bw.ctx.drawImage(bw, 0, 0);
bw.ctx.drawImage(bw, 0, 0);
}
}
bw.ctx.restore();
}
// Using per pixel processing simple threshold.
function twoTonePixelP(bw, threshold) {
const imgD = bw.ctx.getImageData(0, 0, bw.width, bw.height);
const d = imgD.data;
var i = 0;
var v;
while (i < d.length) {
v = (d[i++] + d[i++] + d[i]) < (threshold * 3) ? 0 : 255;
i -= 2;
d[i++] = d[i++] = d[i++] = v;
i++;
}
bw.ctx.putImageData(imgD, 0, 0);
}
//Using per pixel processing with cutoff width
function twoTonePixelCutoff(bw, cutoff, threshold) {
if (cutoff === 0) {
twoTonePixelP(bw, threshold);
return;
}
const eCurve = (v, p) => {
var vv;
return (vv = Math.pow(v, 2)) / (vv + Math.pow(1 - v, 2))
}
const imgD = bw.ctx.getImageData(0, 0, bw.width, bw.height);
const d = imgD.data;
var i = 0;
var v;
const mult = 255 / cutoff;
const offset = -(threshold * mult) + 127;
while (i < d.length) {
v = ((d[i++] + d[i++] + d[i]) / 3) * mult + offset;
v = v < 0 ? 0 : v > 255 ? 255 : eCurve(v / 255) * 255;
i -= 2;
d[i++] = d[i++] = d[i++] = v;
i++;
}
bw.ctx.putImageData(imgD, 0, 0);
}
function OtsuMean(image, type) {
// Otsu's method, from: https://en.wikipedia.org/wiki/Otsu%27s_Method#Variant_2
//
// The input argument pixelsNumber is the number of pixels in the given image. The
// input argument histogram is a 256-element histogram of a grayscale image
// different gray-levels.
// This function outputs the threshold for the image.
function otsu(histogram, pixelsNumber) {
var sum = 0, sumB = 0, wB = 0, wF = 0, mB, mF, max = 0, between, threshold = 0;
for (var i = 0; i < 256; i++) {
wB += histogram[i];
if (wB === 0) continue;
wF = pixelsNumber - wB;
if (wF === 0) break;
sumB += i * histogram[i];
mB = sumB / wB;
mF = (sum - sumB) / wF;
between = wB * wF * Math.pow(mB - mF, 2);
if (between > max) {
max = between;
threshold = i;
}
}
return threshold>>1;
}
const imgD = image.ctx.getImageData(0, 0, image.width, image.height);
const d = imgD.data;
var histogram = new Uint16Array(256);
if(type == 2){
for(var i = 0; i < d.length; i += 4) {
histogram[Math.round(d[i]*.2126+d[i+1]*.7152+d[i+2]*.0722)]++;
}
}else{
for(var i = 0; i < d.length; i += 4) {
histogram[Math.round(Math.sqrt(d[i]*d[i]*.2126+d[i+1]*d[i+1]*.7152+d[i+2]*d[i+2]*.0722))]++;
}
}
return otsu(histogram, image.width * image.height);
}
// finds mean via the perceptual 2,7,1 approx rule rule
function calcMean(image, rule = 0){
if(rule == 2 || rule == 3){
return OtsuMean(image, rule);
}
const imgD = image.ctx.getImageData(0, 0, image.width, image.height);
const d = imgD.data;
var i = 0;
var sum = 0;
var count = 0
while (i < d.length) {
if(rule == 0){
sum += d[i++] * 0.2 + d[i++] * 0.7 + d[i++] * 0.1;
count += 1;
}else{
sum += d[i++] + d[i++] + d[i++];
count += 3;
}
i++;
}
return (sum / count) | 0;
}
// creates a canvas copy of an image.
function makeImageEditable(image) {
const c = document.createElement("canvas");
c.width = (image.width / 2) | 0;
c.height = (image.height / 2) | 0;
c.ctx = c.getContext("2d");
c.ctx.drawImage(image, 0, 0, c.width, c.height);
return c;
}
function updateEditableImage(image,editable) {
editable.width = (image.width / (scaleFull ? 1 : 2)) | 0;
editable.height = (image.height / (scaleFull ? 1 : 2)) | 0;
editable.ctx.drawImage(image, 0, 0, editable.width, editable.height);
}
// load test image and when loaded start UI
function start() {
image.crossOrigin = "anonymous";
image.src = imageSrcs[0];
imageStatus.textContent = "Loading image 1";
image.onload = ()=>{
imageBWA = makeImageEditable(image);
imageBWB = makeImageEditable(image);
imageBWC = makeImageEditable(image);
canA.appendChild(imageBWA);
canB.appendChild(imageBWB);
canC.appendChild(imageBWC);
imageStatus.textContent = "Loaded image 1.";
startUI();
}
}
function selectImage(idx){
imageStatus.textContent = "Loading image " + idx;
image.src = imageSrcs[idx];
image.onload = ()=>{
updateEditableImage(image, imageBWA);
updateEditableImage(image, imageBWB);
updateEditableImage(image, imageBWC);
thresholdC = thresholdB = thresholdA = -1; // force update
imageStatus.textContent = "Loaded image " + idx;
}
}
function toggleScale(){
scaleFull = !scaleFull;
imageStatus.textContent = scaleFull ? "Image full scale." : "Image half scale";
updateEditableImage(image, imageBWA);
updateEditableImage(image, imageBWB);
updateEditableImage(image, imageBWC);
thresholdC = thresholdB = thresholdA = -1; // force update
}
function findMean(e){
imageBWB.ctx.drawImage(image, 0, 0, imageBWB.width, imageBWB.height);
var t = inputThresholdB.value = inputThresholdC.value = calcMean(imageBWB,e.target.dataset.method);
imageStatus.textContent = "New threshold calculated " + t + ". Method : "+ e.target.dataset.name;
thresholdB = thresholdC = -1;
};
// start the UI
function startUI() {
imageControl.className = "imageSel";
selImage1Btn.addEventListener("click",(e)=>selectImage(0));
selImage2Btn.addEventListener("click",(e)=>selectImage(1));
togFullsize.addEventListener("click",toggleScale);
findMean1.addEventListener("click",findMean);
findMean2.addEventListener("click",findMean);
findMean3.addEventListener("click",findMean);
// updates top image
function update1() {
if (amountA !== inputAmountA.value || thresholdA !== inputThresholdA.value) {
amountA = inputAmountA.value;
thresholdA = inputThresholdA.value;
inputAmountValueA.textContent = amountA;
inputThresholdValueA.textContent = thresholdA;
imageBWA.ctx.drawImage(image, 0, 0, imageBWA.width, imageBWA.height);
twoTone(imageBWA, amountA, thresholdA);
}
requestAnimationFrame(update1);
}
requestAnimationFrame(update1);
// updates center image
function update2() {
if (thresholdB !== inputThresholdB.value) {
thresholdB = inputThresholdB.value;
inputThresholdValueB.textContent = thresholdB;
imageBWB.ctx.drawImage(image, 0, 0, imageBWB.width, imageBWB.height);
twoTonePixelP(imageBWB, thresholdB);
}
requestAnimationFrame(update2);
}
requestAnimationFrame(update2);
// updates bottom image
function update3() {
if (cutoffC !== inputCutoffC.value || thresholdC !== inputThresholdC.value) {
cutoffC = inputCutoffC.value;
thresholdC = inputThresholdC.value;
inputCutoffValueC.textContent = cutoffC;
inputThresholdValueC.textContent = thresholdC;
imageBWC.ctx.drawImage(image, 0, 0, imageBWC.width, imageBWC.height);
twoTonePixelCutoff(imageBWC, cutoffC, thresholdC);
}
requestAnimationFrame(update3);
}
requestAnimationFrame(update3);
}
.imageIso {
border: 2px solid black;
padding: 5px;
margin: 5px;
font-size : 12px;
}
.imageSel {
border: 2px solid black;
padding: 5px;
margin: 5px;
}
#imageStatus {
margin: 5px;
font-size: 12px;
}
.btn {
margin: 2px;
font-size : 12px;
border: 1px solid black;
background : white;
padding: 5px;
cursor : pointer;
}
.btn:hover {
background : #DDD;
}
body {
font-family: arial;
font-siae: 12px;
}
canvas {
border: 2px solid black;
padding: 5px;
}
.hide {
display: none;
}
<div class="imageSel hide" id="imageControl">
<input class="btn" id="selImage1Btn" type="button" value="Image 1"></input>
<input class="btn" id="selImage2Btn" type="button" value="Image 2"></input>
<input class="btn" id="togFullsize" type="button" value="Toggle fullsize"></input>
<input class="btn" id="findMean1" type="button" value="Mean M1" data-method=0 data-name="perceptual mean approximation" title="Get the image mean to use as threshold value using perceptual mean approximation"></input>
<input class="btn" id="findMean2" type="button" value="Mean M2" data-method=1 data-name="Pixel RGB sum mean" title="Get threshold value using RGB sum mean"></input>
<input class="btn" id="findMean3" type="button" value="Mean Otsu" data-method=2 data-name="Otsu's method" title="Get threshold value using Otsu's method"></input>
<div id="imageStatus"></div>
</div>
<div class="imageIso">
Using per pixel processing simple threshold. Quick in terms of pixel processing but produces a hard boundary at the threshold value.<br>
<div id="canB"></div>
Threshold<input id="inputThresholdB" type="range" min="1" max="255" step="1" value="128"></input><span id="inputThresholdValueB"></span>
</div>
<div class="imageIso">
Using per pixel processing with cutoff width. This softens the cutoff boundary by gray scaling the values at the threshold.<br>
<div id="canC"></div>
Cutoff width<input id="inputCutoffC" type="range" min="0" max="64" step="0.1" value="8"></input><span id="inputCutoffValueC"></span><br> Threshold
<input id="inputThresholdC" type="range" min="1" max="255" step="1" value="128"></input><span id="inputThresholdValueC"></span>
</div>
<div class="imageIso">
<h2>Means not applied to this image</h2>
Using stacked global composite operations. The quickest method and does not require secure pixel access. Though threshold and cutoff are imprecise.<br>
<div id="canA"></div>
Amount<input id="inputAmountA" type="range" min="1" max="100" step="1" value="75"></input><span id="inputAmountValueA"></span><br> Threshold
<input id="inputThresholdA" type="range" min="1" max="255" step="1" value="127"></input><span id="inputThresholdValueA"></span>
</div>
old answer
The quickest color to black&white using the 2D API
The quickest method for color to BW is as follows
ctx.drawImage(image,0,0);
ctx.globalCompositeOperation = "saturation";
ctx.fillStyle = "#888"; // no saturation
ctx.fillRect(0,0,image.width,image.height);
and provides a good result.
As there is always the debate about which way is the right way the rest of the answer and snippets let you compare the various methods and see which you prefer.
BW conversion comparison.
Many people tend to use the perceptual conversion either as a linear RGB->BW or logarithmic RGB->BW and will swear by it. Personal it's over rated and needs a experiance eye to detect.
There is technically no correct method as the correct method of conversion depends on so many factors, Overall image brightness and contrast, viewing environment's ambient lighting, individual preferences, media type (display, print, other), any existing image processing, origin image source (jpg,png,etc), camera settings, author's intent, viewing context (is it full screen, a thumb, on a bright blue border, etc)
The demo shows some methods of conversion including the common perceptual linear and log, some methods use direct pixel processing via "ctx.getImageData" and others use the GPU via 2D API to do the processing (up 100 times quicker)
Start the snippet the image will load and then be processed. Once all versions are complete they will be display alongside the original. Click on the image to see what function was used and the time it took to process the image.
Image source: served by Wiki. Attribution : public domain.
const methods = {quickBW, quickPerceptualBW, PerceptualLinear, PerceptualLog, directLum, directLumLog}
const image = new Image;
status("Loading test image.");
setTimeout(start,0);
function status(text){
const d = document.createElement("div");
d.textContent = text;
info.appendChild(d);
}
function makeImageEditable(image){
const c = document.createElement("canvas");
c.width = image.width;
c.height = image.height;
c.ctx = c.getContext("2d");
c.ctx.drawImage(image,0,0);
return c;
}
function makeImageSideBySide(image,image1){
const c = document.createElement("canvas");
c.width = image.width + image1.width;
c.height = image.height;
c.ctx = c.getContext("2d");
c.ctx.drawImage(image,0,0);
c.ctx.drawImage(image1,image.width,0);
return c;
}
function text(ctx, text, y = ctx.canvas.height / 2){
ctx.font= "32px arial";
ctx.textAlign = "center";
ctx.fillStyle = "black";
ctx.globalCompositeOperation = "source-over";
ctx.globalAlpha = 1;
ctx.setTransform(1,0,0,1,0,0);
ctx.fillText(text,ctx.canvas.width / 2, y+2);
ctx.fillStyle = "white";
ctx.fillText(text,ctx.canvas.width / 2, y);
}
function quickBW(bw){
bw.ctx.save();
bw.ctx.globalCompositeOperation = "saturation";
bw.ctx.fillStyle = "#888"; // no saturation
bw.ctx.fillRect(0,0,bw.width,bw.height);
bw.ctx.restore();
return bw;
}
function quickPerceptualBW(bw){
bw.ctx.save();
bw.ctx.globalCompositeOperation = "multiply";
var col = "rgb(";
col += ((255 * 0.2126 * 1.392) | 0) + ",";
col += ((255 * 0.7152 * 1.392) | 0) + ",";
col += ((255 * 0.0722 * 1.392) | 0) + ")";
bw.ctx.fillStyle = col;
bw.ctx.fillRect(0,0,bw.width,bw.height);
bw.ctx.globalCompositeOperation = "saturation";
bw.ctx.fillStyle = "#888"; // no saturation
bw.ctx.fillRect(0,0,bw.width,bw.height);
bw.ctx.globalCompositeOperation = "lighter";
bw.ctx.globalAlpha = 0.5;
bw.ctx.drawImage(bw,0,0);
bw.ctx.restore();
return bw;
}
function PerceptualLinear(bw){
const imgD = bw.ctx.getImageData(0,0,bw.width, bw.height);
const d = imgD.data;
var i = 0;
var v;
while(i < d.length){
v = d[i++] * 0.2126 + d[i++] * 0.7152 + d[i] * 0.0722;
i -= 2;
d[i++] = d[i++] = d[i++] = v;
i++;
}
bw.ctx.putImageData(imgD,0,0);
return bw;
}
function PerceptualLog(bw){
const imgD = bw.ctx.getImageData(0,0,bw.width, bw.height);
const d = imgD.data;
var i = 0;
var v;
while(i < d.length){
v = Math.sqrt(d[i] * d[i++] * 0.2126 + d[i] * d[i++] * 0.7152 + d[i] *d[i] * 0.0722);
i -= 2;
d[i++] = d[i++] = d[i++] = v;
i++;
}
bw.ctx.putImageData(imgD,0,0);
return bw;
}
function directLum(bw){
const imgD = bw.ctx.getImageData(0,0,bw.width, bw.height);
const d = imgD.data;
var i = 0;
var r,g,b,v;
while(i < d.length){
r = d[i++];
g = d[i++];
b = d[i];
v = (Math.min(r, g, b) + Math.max(r, g, b)) / 2.2;
i -= 2;
d[i++] = d[i++] = d[i++] = v;
i++;
}
bw.ctx.putImageData(imgD,0,0);
return bw;
}
function directLumLog(bw){
const imgD = bw.ctx.getImageData(0,0,bw.width, bw.height);
const d = imgD.data;
var i = 0;
var r,g,b,v;
while(i < d.length){
r = d[i] * d[i++];
g = d[i] * d[i++];
b = d[i] * d[i];
v = Math.pow((Math.min(r, g, b) + Math.max(r, g, b)/2) ,1/2.05);
i -= 2;
d[i++] = d[i++] = d[i++] = v;
i++;
}
bw.ctx.putImageData(imgD,0,0);
return bw;
}
function start(){
image.crossOrigin = "Anonymous"
image.src = "https://upload.wikimedia.org/wikipedia/en/2/24/Lenna.png";
status("Image loaded, pre processing.");
image.onload = ()=>setTimeout(process,0);
}
function addImageToDOM(element,data){
element.style.width = "512px"
element.style.height = "256px"
element.addEventListener("click",()=>{
text(element.ctx,"Method : " + data.name + " Time : " + data.time.toFixed(3) + "ms",36);
});
document.body.appendChild(element);
}
function process(){
const pKeys = Object.keys(methods);
const images = pKeys.map(()=>makeImageEditable(image));
const results = {};
status("Convert to BW");
setTimeout(()=>{
pKeys.forEach((key,i)=>{
const now = performance.now();
methods[key](images[i]);
results[key] = {};
results[key].time = performance.now() - now;
results[key].name = key;
});
pKeys.forEach((key,i)=>{
addImageToDOM(makeImageSideBySide(images[i],image),results[key]);
})
status("Complete!");
status("Click on image that you think best matches");
status("The original luminance to see which method best suits your perception.");
status("The function used and the time to process in ms 1/1000th sec");
},1000);
}
canvas {border : 2px solid black;}
body {font-family : arial; font-size : 12px; }
<div id="info"></div>

Automatically contrasting text color based on background image color

I'm looking for a way to change the color of text to either #000 or #fff depending on the main color of a background image within a div called "banner". The background images are chosen at random on each page so I need to be able to do this automatically. I came across JavaScript color contraster but I'm struggling to understand how to use it properly. I notice the link I've posted gives a solution in javascript and I've also read about a possible solution in jquery.
I'm clueless with functions so if anyone could explain clearly how I could achieve this, where I place functions and how I "call them" (if that's the right term!) to use it I'd be really grateful.
Thanks for any help.
You could do something like this. (using Colours.js and this answer)
Note, this will only work with images on the same domain and in browsers that support HTML5 canvas.
'use strict';
var getAverageRGB = function(imgEl) {
var rgb = {
b: 0,
g: 0,
r: 0
};
var canvas = document.createElement('canvas');
var context = canvas.getContext && canvas.getContext('2d');
if (Boolean(context) === false) {
return rgb;
}
var height = imgEl.naturalHeight || imgEl.offsetHeight || imgEl.height;
var width = imgEl.naturalWidth || imgEl.offsetWidth || imgEl.width;
canvas.height = height;
canvas.width = width;
context.drawImage(imgEl, 0, 0);
var data;
try {
data = context.getImageData(0, 0, width, height).data;
} catch (e) {
console.error('security error, img on diff domain');
return rgb;
}
var count = 0;
var length = data.length;
// only visit every 5 pixels
var blockSize = 5;
var step = (blockSize * 4) - 4;
for (var i = step; i < length; i += step) {
count += 1;
rgb.r += data[i];
rgb.g += data[i + 1];
rgb.b += data[i + 2];
}
rgb.r = Math.floor(rgb.r / count);
rgb.g = Math.floor(rgb.g / count);
rgb.b = Math.floor(rgb.b / count);
return rgb;
};
var rgb = getAverageRGB(document.getElementById('image'));
var avgComplement = Colors.complement(rgb.r, rgb.b, rgb.g);
var avgComplementHex = Colors.rgb2hex.apply(null, avgComplement.a);
var compliment = parseInt(avgComplementHex.slice(1), 16);
document.body.style.backgroundColor = 'rgb(' + [
rgb.r,
rgb.g,
rgb.b
].join(',') + ')';
var maxColors = 0xFFFFFF;
var midPoint = Math.floor(maxColors / 2);
document.getElementById('text').style.color = compliment > midPoint ? '#000' : '#fff';
<script src="https://cdnjs.cloudflare.com/ajax/libs/Colors.js/1.2.3/colors.min.js"></script>
<div id="text">Setting the BODY's background to the average color in the following image and this text to a complimentary colour of black or white:</div>
<img id="image" src="data:image/jpeg;base64,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" />

Dynamically adjust text color based on background image

I am working on a product that outputs images from users and the image information is overlayed on top of the aforementioned images. As you might imagine, the images require different text colors due to lightness/darkness. Is there a way to achieve this with JavaScript?
EDIT: I found a similar question to mine and there was a solution given in a jsfiddle (http://jsfiddle.net/xLF38/818). I am using jQuery for my site though. How would I convert the vanilla JavaScript to jQuery?
var rgb = getAverageRGB(document.getElementById('i'));
document.body.style.backgroundColor = 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')';
function getAverageRGB(imgEl) {
var blockSize = 5, // only visit every 5 pixels
defaultRGB = {
r: 0,
g: 0,
b: 0
}, // for non-supporting envs
canvas = document.createElement('canvas'),
context = canvas.getContext && canvas.getContext('2d'),
data, width, height,
i = -4,
length,
rgb = {
r: 0,
g: 0,
b: 0
},
count = 0;
if (!context) {
return defaultRGB;
}
height = canvas.height = imgEl.naturalHeight || imgEl.offsetHeight || imgEl.height;
width = canvas.width = imgEl.naturalWidth || imgEl.offsetWidth || imgEl.width;
context.drawImage(imgEl, 0, 0);
try {
data = context.getImageData(0, 0, width, height);
} catch (e) {
/* security error, img on diff domain */
alert('x');
return defaultRGB;
}
length = data.data.length;
while ((i += blockSize * 4) < length) {
++count;
rgb.r += data.data[i];
rgb.g += data.data[i + 1];
rgb.b += data.data[i + 2];
}
// ~~ used to floor values
rgb.r = ~~ (rgb.r / count);
rgb.g = ~~ (rgb.g / count);
rgb.b = ~~ (rgb.b / count);
return rgb;
}
I finally found something to do precisely what I want it to do! Enter Brian Gonzalez's
jquery.adaptive-backgrounds.js. Check this out:
$parent.css({
// backgroundColor: data.color
color: data.color
});
I just commented out the backgroundColor rule and made a new one for color. For white text, a text-shadow like:
text-shadow: 0 0 1px rgba($black, 0.3); // using Sass
should be enough. Thank you to everyone for your answers!
This is possible using the canvas element. You would have to create a canvas element, draw the image element into the canvas, get the canvas's image data, look at the portion where the text is, convert those values to grayscale, average them, then compare them with a halfway point. Some example code:
var img = document.getElementById('myImage');
var c = document.createElement('canvas');
var ctx = c.getContext('2d');
var w = img.width, h = img.height;
c.width = w; c.height = h;
ctx.drawImage(img, 0, 0);
var data = ctx.getImageData(0, 0, w, h).data;
var brightness = 0;
var sX = 0, sY = 0, eX = w, eY = h;
var start = (w * sY + sX) * 4, end = (w * eY + eX) * 4;
for (var i = start, n = end; i < n; i += 4) {
var r = data[i],
g = data[i + 1],
b = data[i + 2];
brightness += 0.34 * r + 0.5 * g + 0.16 * b;
if (brightness !== 0) brightness /= 2;
}
if (brightness > 0.5) var textColor = "#FFFFFF";
else var textColor = "#000000";
I haven't tested this code, though it should work. Make sure to change the sX, sY, eX, eY values to only the area where your text is, otherwise you will get unsatisfactory results (it will still work). Good luck!
EDIT:
You will not have to display your image in any special way. Just make sure that the color of the overlay text is the variable textColor.
you could check the background-image attribute with jQuery then adjust the text color dynamically.
var x = $(body).attr("background-image");
switch(x)
{
case "something.png":
// set color here
break;
}

Get a pixel from HTML Canvas?

Is it possible to query a HTML Canvas object to get the color at a specific location?
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);
Try the getImageData method:
var data = context.getImageData(x, y, 1, 1).data;
var rgb = [ data[0], data[1], data[2] ];
Yes sure, provided you have its context. (See how to get canvas context here.)
var imgData = context.getImageData(0,0,canvas.width,canvas.height)
// { data: [r,g,b,a,r,g,b,a,r,g,..], ... }
function getPixel(imgData, index) {
var i = index*4, d = imgData.data
return [d[i],d[i+1],d[i+2],d[i+3]] // Returns array [R,G,B,A]
}
// AND/OR
function getPixelXY(imgData, x, y) {
return getPixel(imgData, y*imgData.width+x)
}
PS: If you plan to mutate the data and draw them back on the canvas, you can use subarray
var
idt = imgData, // See previous code snippet
a = getPixel(idt, 188411), // Array(4) [0, 251, 0, 255]
b = idt.data.subarray(188411*4, 188411*4 + 4) // Uint8ClampedArray(4) [0, 251, 0, 255]
a[0] = 255 // Does nothing
getPixel(idt, 188411) // Array(4) [0, 251, 0, 255]
b[0] = 255 // Mutates the original imgData.data
getPixel(idt, 188411) // Array(4) [255, 251, 0, 255]
// Or use it in the function
function getPixel(imgData, index) {
var i = index*4, d = imgData.data
return imgData.data.subarray(i, i+4) // Returns subarray [R,G,B,A]
}
You can experiment with this on http://qry.me/xyscope/, the code for this is in the source, just copy/paste it in the console.
function GetPixel(context, x, y)
{
var p = context.getImageData(x, y, 1, 1).data;
var hex = "#" + ("000000" + rgbToHex(p[0], p[1], p[2])).slice(-6);
return hex;
}
function rgbToHex(r, g, b) {
if (r > 255 || g > 255 || b > 255)
throw "Invalid color component";
return ((r << 16) | (g << 8) | b).toString(16);
}
Yup, check out getImageData(). Here's an example of breaking CAPTCHA with JavaScript using canvas:
OCR and Neural Nets in JavaScript
Note that getImageData returns a snapshot. Implications are:
Changes will not take effect until subsequent putImageData
getImageData and putImageData calls are relatively slow
//Get pixel data
var imageData = context.getImageData(x, y, width, height);
//Color at (x,y) position
var color = [];
color['red'] = imageData.data[((y*(imageData.width*4)) + (x*4)) + 0];
color['green'] = imageData.data[((y*(imageData.width*4)) + (x*4)) + 1];
color['blue'] = imageData.data[((y*(imageData.width*4)) + (x*4)) + 2];
color['alpha'] = imageData.data[((y*(imageData.width*4)) + (x*4)) + 3];
You can use i << 2.
const data = context.getImageData(x, y, width, height).data;
const pixels = [];
for (let i = 0, dx = 0; dx < data.length; i++, dx = i << 2) {
pixels.push({
r: data[dx ],
g: data[dx+1],
b: data[dx+2],
a: data[dx+3]
});
}
Fast and handy
Use following class which implement fast method described in this article and contains all you need: readPixel, putPixel, get width/height. Class update canvas after calling refresh() method. Example solve simple case of 2d wave equation
class Screen{
constructor(canvasSelector) {
this.canvas = document.querySelector(canvasSelector);
this.width = this.canvas.width;
this.height = this.canvas.height;
this.ctx = this.canvas.getContext('2d');
this.imageData = this.ctx.getImageData(0, 0, this.width, this.height);
this.buf = new ArrayBuffer(this.imageData.data.length);
this.buf8 = new Uint8ClampedArray(this.buf);
this.data = new Uint32Array(this.buf);
}
// r,g,b,a - red, gren, blue, alpha components in range 0-255
putPixel(x,y,r,g,b,a=255) {
this.data[y * this.width + x] = (a << 24) | (b << 16) | (g << 8) | r;
}
readPixel(x,y) {
let p= this.data[y * this.width + x]
return [p&0xff, p>>8&0xff, p>>16&0xff, p>>>24];
}
refresh() {
this.imageData.data.set(this.buf8);
this.ctx.putImageData(this.imageData, 0, 0);
}
}
// --------
// TEST
// --------
let s=new Screen('#canvas');
function draw() {
for (var y = 1; y < s.height-1; ++y) {
for (var x = 1; x < s.width-1; ++x) {
let a = [[1,0],[-1,0],[0,1],[0,-1]].reduce((a,[xp,yp])=>
a+= s.readPixel(x+xp,y+yp)[0]
,0);
let v=a/2-tmp[x][y];
tmp[x][y]=v<0 ? 0:v;
}
}
for (var y = 1; y < s.height-1; ++y) {
for (var x = 1; x < s.width-1; ++x) {
let v=tmp[x][y];
tmp[x][y]= s.readPixel(x,y)[0];
s.putPixel(x,y, v,v,v);
}
}
s.refresh();
window.requestAnimationFrame(draw)
}
// temporary 2d buffer ()for solving wave equation)
let tmp = [...Array(s.width)].map(x => Array(s.height).fill(0));
function move(e) { s.putPixel(e.x-10, e.y-10, 255,255,255);}
draw();
<canvas id="canvas" height="150" width="512" onmousemove="move(event)"></canvas>
<div>Move mouse on black box</div>
If you want to extract a particular color of pixel by passing the coordinates of pixel into the function, this will come in handy:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
function detectColor(x, y){
data=ctx.getImageData(x, y, 1, 1).data;
col={
r:data[0],
g:data[1],
b:data[2]
};
return col;
}
x, y is the coordinate you want to filter out color.
var color = detectColor(x, y)
The color is the object, you will get the RGB value by color.r, color.g, color.b.

Categories

Resources