I have a raindow HSV gradient canvas that when you click it, an element is added at that location with its background as the color of the clicked pixel.
What I’d like is to have it work in reverse as well. For example if you had a hex color, I’d like to find that pixel on the canvas and create an element at that position.
My first thought was to somehow use a matrix/quadrant system. My next thought was that since I’m using HSV, I could use my HSV gradient location points to figure out the location. The problem is that my points aren’t equidistant from each other which makes it harder. On top of that, I have a white gradient and black gradient covering the main color gradient and I need that to be accounted for.
So my question is, how can I find the position of the color pixel or at least it’s closest match by just using a hex code?
Here is my code thus far:
http://codepen.io/shelbywhite/pen/EyqPWY?editors=1000
HTML:
<div class="container">
<canvas class="colorSpectrum"></canvas>
<div class="circle"></div>
</div>
CSS:
.container {
background: grey;
height: 350px;
width: 400px;
}
.circle {
background: transparent;
box-shadow: 0 0 8px rgba(0,0,0,0.2);
border-radius: 50%;
border: 2px solid #fff;
height: 20px;
margin: -12px;
width: 20px;
position: absolute;
}
.colorSpectrum {
display: block;
height: 100%;
transform: translateZ(0);
width: 100%;
}
Javascript:
$(function() {
var closest = function(num, arr) {
var curr = arr[0];
var diff = Math.abs(num - curr);
for (var val = 0; val < arr.length; val++) {
var newdiff = Math.abs(num - arr[val]);
if (newdiff < diff) {
diff = newdiff;
curr = arr[val];
}
}
return curr;
};
var container = $('.container');
var containerWidth = container.width();
var containerHeight = container.height();
var verticalGradientsHeight = Math.round(containerHeight * .34);
console.log('verticalGradientsHeight', verticalGradientsHeight);
var round = function(value, decimals) {
return Number(Math.round(value+'e'+decimals)+'e-'+decimals);
};
// Draws the color spectrum onto the canvas
var drawColorSpectrum = function() {
// Cache canvas element
var canvasElement = $('.colorSpectrum');
// Cache javascript element
var canvas = canvasElement[0];
// Get canvas context
var ctx = canvas.getContext('2d');
// Cache page height
var canvasWidth = containerWidth;
// Cache page height
var canvasHeight = containerHeight - 72;
// Bottom gradient start position
var blackStartYPos = canvasHeight - verticalGradientsHeight;
// Bottom gradient end position
var blackEndYPos = canvasHeight;
// Create white gradient element
var white = ctx.createLinearGradient(0, 0, 0, verticalGradientsHeight);
// Create black gradient element
var black = ctx.createLinearGradient(0, blackStartYPos, 0, blackEndYPos);
// Create new instance of image
var img = new Image();
// Cache container
_colorSpectrumContainer = canvasElement.parent();
// Set global var
spectrumCanvas = canvasElement;
// Set width of canvas
canvas.width = canvasWidth;
// Set height of canvas
canvas.height = canvasHeight;
// Image load listener
img.onload = function() {
// Draw intial image
ctx.drawImage(this, 0, 0, canvasWidth, canvasHeight);
// Draw white to transparent gradient
white.addColorStop(0, "hsla(0,0%,100%,1)");
white.addColorStop(0.05, "hsla(0,0%,100%,1)");
white.addColorStop(0.20, "hsla(0,0%,100%,0.89)");
white.addColorStop(0.38, "hsla(0,0%,100%,0.69)");
white.addColorStop(0.63, "hsla(0,0%,100%,0.35)");
white.addColorStop(0.78, "hsla(0,0%,100%,0.18)");
white.addColorStop(0.91, "hsla(0,0%,100%,0.06)");
white.addColorStop(1, "hsla(0,0%,100%,0)");
ctx.fillStyle = white;
ctx.fillRect(0, 0, canvasWidth, verticalGradientsHeight);
// Draw black to transparent gradient
black.addColorStop(0, "hsla(0,0%,0%,0)");
black.addColorStop(0.20, "hsla(0,0%,0%,0.01)");
black.addColorStop(0.28, "hsla(0,0%,0%,0.04)");
black.addColorStop(0.35, "hsla(0,0%,0%,0.09)");
black.addColorStop(0.51, "hsla(0,0%,0%,0.26)");
black.addColorStop(0.83, "hsla(0,0%,0%,0.69)");
black.addColorStop(1, "hsla(0,0%,0%,1)");
ctx.fillStyle = black;
ctx.fillRect(0, blackStartYPos, canvasWidth, verticalGradientsHeight);
}
// Set image source
img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAAABCAYAAACbv+HiAAAA0ElEQVR4AYWSh2oDMAwFz6u7//+d2YmXalGBIBM47nnPIIEtmd8FGBTgDbPxDmbn49pX+cZX+Nz4mkZ2SECEAXTCAprlalntBC5whdUJnOfKEy5DjZYtB+o0D3XUMk0tkaZZEn2VuyiJQQQywS/P4c25ucTrfF3ndsoVdjmy3NMiuptR1eHfNcBFM2orW1ZXru00JZiBDrIII5AG5AlloX5TcG6/ywuuv0zAbyL4TWRZmIvU5TNBTjCPIIu5N3YgO7Wxtbot3q4+2LgTyFnZ/QHzBZD1KDpyqQAAAABJRU5ErkJggg==";
};
//
var hexToRgb = function(hex) {
hex = hex.replace('#','');
r = parseInt(hex.substring(0, 2), 16);
g = parseInt(hex.substring(2, 4), 16);
b = parseInt(hex.substring(4, 6), 16);
return [r, g, b];
};
//
var rgbToHsb = function(r, g, b) {
var rr, gg, bb,
r = r / 255,
g = g / 255,
b = b / 255,
h, s,
v = Math.max(r, g, b),
diff = v - Math.min(r, g, b),
diffc = function(c){
return (v - c) / 6 / diff + 1 / 2;
};
if (diff == 0) {
h = s = 0;
} else {
s = diff / v;
rr = diffc(r);
gg = diffc(g);
bb = diffc(b);
if (r === v) {
h = bb - gg;
}else if (g === v) {
h = (1 / 3) + rr - bb;
}else if (b === v) {
h = (2 / 3) + gg - rr;
}
if (h < 0) {
h += 1;
}else if (h > 1) {
h -= 1;
}
}
return {
h: Math.round(h * 360),
s: Math.round(s * 100),
b: Math.round(v * 100)
};
};
// Find hue in stop range
var findHueInStopRange = function(hue) {
// Array of hue stops with HSV, RGB, and HEX info
var stops = [{
h: 0,
l: 0,
s: 100,
b: 100
}, {
h: 60,
l: 21,
s: 100,
b: 100
}, {
h: 120,
l: 40,
s: 85,
b: 85
}, {
h: 180,
l: 56,
s: 85,
b: 85
}, {
h: 237,
l: 72,
s: 86,
b: 96
}, {
h: 300,
l: 89,
s: 86,
b: 96
}, {
h: 359,
l: 100,
s: 100,
b: 100
}];
// Total number of stops
var stopsLength = stops.length;
// Loop through stops
for (var i = 0; i < stopsLength; i += 1) {
// Temp set
var currentStop = stops[i];
// Temp set
// var nextStop = stops[i + 1];
var nextStop = (i + 1 > stopsLength - 1) ? currentStop : stops[i + 1];
// Location is a percentage
var huePos;
// Temp set
var xPos = false;
console.log('hue', currentStop.h, '>>', hue, '<<', nextStop.h);
// Find which range of hue stops the current color is
// Hue is between current and next hue stop
if (hue >= currentStop.h && hue <= nextStop.h) {
// hue is current stop
if (hue === currentStop.h) {
// Set as location
huePos = currentStop.l;
// hue is next stop
} else if (hue === nextStop.h) {
// Set as location
huePos = nextStop.l;
// Hue is somewhere between stops
} else {
// Get percentage location between hue stops
var relativeHuePos = (hue - currentStop.h) / (nextStop.h - currentStop.h);
// Normalized to fit custom gradient stop locations
huePos = relativeHuePos * (nextStop.l - currentStop.l) + currentStop.l;
}
// A location was found
if (huePos) {
// Convert from percentage to pixel position
xPos = Math.round(containerWidth * (huePos / 100));
return xPos;
} else {
continue;
}
}
}
};
// Find saturation in stop range
var findSaturationInStopRange = function (saturation) {
// Array of hue stops with HSV, RGB, and HEX info
var stops = [{
l: 0,
s: 0
}, {
l: 0.05,
s: 6
}, {
l: 0.20,
s: 18
}, {
l: 0.38,
s: 35
}, {
l: 0.63,
s: 69
}, {
l: 0.78,
s: 89,
}, {
l: 0.91,
s: 100,
}, {
l: 1,
s: 100,
}];
// Total number of stops
var stopsLength = stops.length;
// Loop through stops
for (var i = 0; i < stopsLength; i += 1) {
// Temp set
var currentStop = stops[i];
// Temp set
var nextStop = (i + 1 > stopsLength - 1) ? currentStop : stops[i + 1];
// Location is a percentage
var satPos;
// Temp set
var yPos = false;
// Convert location to percentage
var currentStopLocation = currentStop.l * 100;
// Convert location to percentage
var nextStopLocation = nextStop.l * 100;
// Find which range of hue stops the current color is
// Hue is between current and next hue stop
if (saturation >= currentStop.s && saturation <= nextStop.s) {
// hue is current stop
if (saturation === currentStop.s) {
// Set as location
satPos = currentStopLocation;
// hue is next stop
} else if (saturation === nextStop.s) {
// Set as location
satPos = nextStopLocation;
// Hue is somewhere between stops
} else {
// Get percentage location between gradient stops
var ratioBetweenSaturation = (saturation - currentStop.s) / (nextStop.s - currentStop.s);
// Normalized to fit custom gradient stop locations
satPos = ratioBetweenSaturation * (nextStopLocation - currentStopLocation) + currentStopLocation;
}
console.log('ratioBetweenSaturation', ratioBetweenSaturation);
console.log('satPos', satPos);
console.log('saturation', saturation, '>=', currentStop.s, saturation, '<=', nextStop.s);
// A location was found
if (satPos !== false) {
// Convert from percentage to pixel position
yPos = Math.round(verticalGradientsHeight * (satPos / 100));
return yPos;
} else {
continue;
}
}
}
};
// Find brightness in stop range
var findBrightnessInStopRange = function (brightness) {
// Array of hue stops with HSV, RGB, and HEX info
var stops = [{
l: 0,
b: 100
}, {
l: 0.20,
b: 88
}, {
l: 0.28,
b: 69
}, {
l: 0.35,
b: 26
}, {
l: 0.51,
b: 9
}, {
l: 0.83,
b: 4,
}, {
l: 1,
b: 0,
}];
// Total number of stops
var stopsLength = stops.length;
// Loop through stops
for (var i = 0; i < stopsLength; i += 1) {
// Temp set
var currentStop = stops[i];
// Temp set
var nextStop = (i + 1 > stopsLength - 1) ? currentStop : stops[i + 1];
// Location is a percentage
var brightPos;
// Temp set
var yPos = false;
// Convert location to percentage
var currentStopLocation = currentStop.l * 100;
// Convert location to percentage
var nextStopLocation = nextStop.l * 100;
console.log('brightness', brightness, '>=', currentStop.b, brightness, '<=', nextStop.b);
// Find which range of hue stops the current color is
// Hue is between current and next hue stop
if (brightness <= currentStop.b && brightness >= nextStop.b) {
// hue is current stop
if (brightness === currentStop.b) {
// Set as location
brightPos = currentStopLocation;
// hue is next stop
} else if (brightness === nextStop.b) {
// Set as location
brightPos = nextStopLocation;
// Hue is somewhere between stops
} else {
// Get percentage location between gradient stops
var ratioBetweenBrightness = (brightness - currentStop.b) / (nextStop.b - currentStop.b);
// Normalized to fit custom gradient stop locations
brightPos = ratioBetweenBrightness * (nextStopLocation - currentStopLocation) + currentStopLocation;
}
console.log('ratioBetweenBrightness', ratioBetweenBrightness);
console.log('brightPos', brightPos);
console.log('brightness', brightness, '>=', currentStop.b, brightness, '<=', nextStop.b);
// A location was found
if (brightPos !== false) {
// Convert from percentage to pixel position
yPos = Math.round(verticalGradientsHeight * (brightPos / 100));
return yPos;
} else {
continue;
}
}
}
};
// Get coordinates from hue, brightness, saturation
var getColorCoordinates = function (hex) {
// Convert hex to rgb
var rgb = hexToRgb(hex);
console.log('rgb', rgb);
// Convert rgb to hsb
var hsb = rgbToHsb(rgb[0], rgb[1], rgb[2]);
console.log('hsb', hsb);
// Set x position to position of hue
var xPos = findHueInStopRange(hsb.h);
var yPos = 0;
// if 100, get (containerHeight - verticalGradientHeight) + whatever position is set with bottom gradient
//
// Saturation and brightness are both maxed
if (hsb.s === 100 && hsb.b === 100) {
// Set y position at center of container
yPos = containerHeight * 0.5;
} else {
console.log('using nothing', hsb.s, hsb.b);
//
if (hsb.s < 100) {
// Saturation y position (upper quadrant)
yPos = findSaturationInStopRange(hsb.s);
console.log('using saturation', yPos);
} else if (hsb.b < 100) {
// Brightness y position (lower quadrant)
yPos = findBrightnessInStopRange(hsb.b);
console.log('using brightness', yPos);
}
}
return { x: xPos, y: yPos };
}
// Get hue location
var position = false;
// Temp set
var hex = '42ad40';
// Draw gradient
drawColorSpectrum();
// Find x position
position = getColorCoordinates(hex); //91ff26
console.log('location', position);
// Draw line
$('.circle').css({
top: position.y + 'px',
left: position.x + 'px',
background: '#' + hex
});
});
** UPDATE **
I’m actually trying to do this in HSV not HSL. I don’t have preference other than I used photoshop to generate a smooth gradient.
Also, I have added a new example with what I have created. One of the up-voted answers below hints at how to accomplish what I’m trying to do, but so far I haven’t been able to successfully do it.
Updated code will be at this link, I’ve also updated the code above:
http://codepen.io/shelbywhite/pen/EyqPWY?editors=1000
2
Answers
To get the location you need the HSL values
Hue will give the position on the x axis and Saturation will give you y from top to midway and Lightness will give you y axis position from midway to the bottom (going by your example);
You could iterate over the entire
ImageData
and compare both colors.Note that this is pretty slow and not the best idea.