Calculate a colour in a linear gradient - node.js

I'd like to implement something like the powerpoint image below. A gradient that goes between three values.
It starts at A (-1), the mid point is B (0), and the end is C (1).
I have realised that I can save some effort by calculating the 'start' as a-to-b, and the 'end' as b-to-c. I can do as 2 sets of 2 gradients, instead of 1 gradient with three values.
But I'm stumped (despite googling) on how to get from one colour to another - ideally in the RGB colour space.
I'd like to be able to have something like this -
const colourSpace = (value, startColor, endColor) => {...}
colorSpace(-0.25, red, yellow) // some sort of orangey color
colorSpace(1, yellow, green) // fully green
colorSpace(0.8, yellow, green) // mostly green
This isn't a front-end application, so no CSS gradients - which is what google was mostly referencing.
Thanks all,
Ollie

If you aren't too worried about being perceptually consistent across the color space (you would need to work in something like LAB mode to do that), you can just take the linear interpolation in RGB space. Basically you take a distance (between 0 and 1), multiply it by the different in the coordinates, and add it to the first one. This will allow you to find arbitrary points (i.e colors) along the line between any two colors.
For example between red and yellow:
let canvas = document.getElementById('canvas')
var ctx = canvas.getContext('2d');
let rgb1 = [255, 0, 0] // red
let rgb2 = [255, 255, 0] // yellow
function getPoint(d, a1, a2) {
// find a color d% between a1 and a2
return a1.map((p, i) => Math.floor(a1[i] + d * (a2[i] - a1[i])))
}
// for demo purposes fill a canvas
for (let i = 0, j = 0; i < 1; i += .002, j++) {
let rgb = getPoint(i, rgb1, rgb2)
ctx.fillStyle = `rgba(${rgb.join(",")}, 1)`
ctx.fillRect(j, 0, 1, 200);
}
<canvas id="canvas" width="500"></canvas>
You can repeat this to get multiple 'stops' in the gradient.

I ended up using Chroma for converting between colour spaces.

Related

Create a colour histogram from an image file

I'd like to use Nim to check the results of my Puppeteer test run executions.
Part of the end result is a screenshot. That screenshot should contain a certain amount of active colours. An active colour being orange, blue, red, or green. They indicate activity is present in the incoming data. Black, grey, and white need to be excluded, they only represent static data.
I haven't found a solution I can use yet.
import stb_image/read as stbi
var
w, h , c:int
data: seq[uint8]
cBin: array[256,int] #colour range was 0->255 afaict
data = stbi.load("screenshot.png",w,h,c,stbi.Default)
for d in data:
cBin[(int)d] = cBin[(int)d] + 1
echo cBin
Now I have a uint array, which I can see I can use to construct a histogram of the values, but I don't know how to map these to something like RGB values. Pointers anyone?
Is there a better package which has this automagically, I didn't spot one.
stbi.load() will return a sequence of interleaved uint8 color components. The number of interleaved components is determined either by c (i.e. channels_in_file) or desired_channels when it is non-zero.
For example, when channels_in_file == stbi.RGB and desired_channels == stbi.Default there are 3 interleaved components of red, green, and blue.
[
# r g b
255, 0, 0, # Pixel 1
0, 255, 0, # Pixel 2
0, 0, 255, # Pixel 3
]
You can process the above like:
import colors
for i in countUp(0, data.len - 3, step = stbi.RGB):
let
r = data[i + 0]
g = data[i + 1]
b = data[i + 2]
pixelColor = colors.rgb(r, g, b)
echo pixelColor
You can read more on this within comments for the stb_image.h.

Detect a colour based on its RGB

I want to detect red and green objects from webcam. I am using this Trackingjs library. I think with the library, it will track the red, green and blue value of an object and detect the colour according to the built-in colour library in their code.
Their built-in colours are defined like this:
tracking.ColorTracker.registerColor('purple', function(r, g, b) {
var dx = r - 120;
var dy = g - 60;
var dz = b - 210;
if ((b - g) >= 100 && (r - g) >= 60) {
return true;
}
return dx * dx + dy * dy + dz * dz < 3500;
});
In the example above, objects which has their blue - green >= 100 and red -green >= 60 will be purple.
Can someone explain this to me how this works.
And if I want to detect red/ reddish and green/greenish, how should I do with the same function.
If you look at the colour RGB(120,60,210) it is this purple:
Now, dx measures the distance that the passed in colour is from the red component in that purple, dy measures the distance that the passed in colour is from the green component of that purple and dz measures the distance that the passed in colour is from the blue component.
The return value at the end, calculates, using 3-D Pythagorus, the square of the distance that the passed in colour is from purple in the 3-D colour cube with black at one corner, white diagonally opposite and R, G and B at the corners. So, it effectively defines a "sphere" of colour around the purple.
If you want to detect reddish tones, you will want something like
if ((r-g)>50 && (r-b)>50) { return true;}
which just says you want there to be more red than green and more red than blue.
If you want to detect greenish tones, you will want something like
if ((g-r)>50 && (g-b)>50) { return true;}
which just says you want there to be more green than red and more green than blue.
RGB Colour Parser.
this is what you need. And of course it can be used for color verification.
From the description:
A JavaScript class that accepts a string and tries to figure out a valid color out of it. Some accepted inputs are for example:
rgb(0, 23, 255)
#336699
ffee66
fb0
red
darkblue
cadet blue
DEMO: Demo

Don't understand hsv color palette

i test a lot converters hex to hsv rgb to hsv and other options. But don't understand situation i have paint program which i see use HSV palette. i use TinyColor converter. I don't know why i sometimes get good color, sometimes not good.
This return good result red color:
var color = tinycolor("#FF0000"); //red
color.toHsv(); // return { h: 0, s: 1, v: 1 }
This return bad result not yellow color:
var color = tinycolor("#FFFF00"); //yellow
color.toHsv(); // return { h: 60, s: 1, v: 1 } and i get not yellow color
If i write in my hsv input like this:
h: 0.16
s: 1
v: 1
i get yellow collor WTF?
I see in my HSV palette i can write only one digit numbers like this:
1, 0.1, 0.99, max is 1 min is 0.00
Hue, the h in hsv, is traditionally expressed in degrees around a circle — the color wheel, which means it can have a value from 0º - 360º. See: http://en.wikipedia.org/wiki/Hue
It is sometimes convenient to express this as a percentage instead where 0= 0º, 0.5 = 180º, 1.0 = 360º, etc. The documentation for TinyColor explains that it will accept either input, but it is not clear what its default output is (at least from my quick scan).
It seem to be returning degrees, but your other application is expecting a percentage. A 60º hue is yellow, but you may need need to convert to a percentage for whatever application you're using with the hsv palette.
In this particular case, 60º/360º = 0.1667

d3 color scale - linear with multiple colors?

I'm trying to create something a little like a quantize scale, but act like a linear color scale?
When I try to put multiple colors into a linear scale, it seems to only scale between the first two colors.
I'd like multiple colors like a quantize scale, but fade between those colors. I'm unsure if this is possible.
//red and green works ok
var color = d3.scale.linear()
.range(['red','green']);
//doesn't work - only red and green show
var color = d3.scale.linear()
.range(['red','green', 'blue']);
//red green and blue show, however it doesn't fade between the colors
var color = d3.scale.quantize()
.range(['red','green', 'blue']);
You have to use domain 'pivot' value like:
d3.scale.linear()
.domain([0, pivot, max])
.range(['red', 'green', 'blue']);
From the documentation for continuous scale domains:
Although continuous scales typically have two values each in their domain and range, specifying more than two values produces a piecewise scale. For example, to create a diverging color scale that interpolates between white and red for negative values, and white and green for positive values, say:
var color = d3.scaleLinear()
.domain([-1, 0, 1])
.range(["red", "white", "green"]);
color(-0.5); // "rgb(255, 128, 128)"
color(+0.5); // "rgb(128, 192, 128)"
Anto's solution works great if you want to blend 3 colors. In my case, I needed a way to blend an arbitrary number of colors. For me, the trick was to set up the domain correctly. Take for example, the following array of colors:
var colors = ['#084594', '#2171b5', '#4292c6', '#6baed6', '#9ecae1', '#c6dbef', '#eff3ff'];
You can create a domain array with values from -1 to +1 like this:
var domain = [-1];
var increment = 2/(colors.length-1);
for (var i=0; i<colors.length-2; i++){
var previous = domain[domain.length-1];
domain.push(previous+increment);
}
domain.push(1);
Once the domain array is created, you can create a color function like this:
var getColor = d3.scaleLinear()
.domain(domain)
.range(colors);
If you want to get color values at specific percentages (like chroma does), you can do something like this:
var p = 0.25; //Valid range for p is 0.0-1.0
var x = (p*2)-1;
var color = d3.color(getColor(x));
console.log(color.formatHex());

How to generate a set of random colors where no two colors are almost similar?

I currently use the following function to generate a random hexadecimal representation of a color.
function getRandomColor($max_r = 192, $max_g = 192, $max_b = 192) {
if ($max_r > 192) { $max_r = 192; }
if ($max_g > 192) { $max_g = 192; }
if ($max_b > 192) { $max_b = 192; }
if ($max_r < 0) { $max_r = 0; }
if ($max_g < 0) { $max_g = 0; }
if ($max_b < 0) { $max_b = 0; }
return '#' . dechex(rand(0, 192)) . dechex(rand(0, 192)) . dechex(rand(0, 192));
}
Notice that I set the max value to be 192 instead of 255 for the sole reason that I am avoiding very light colors, for the purpose that I would be using the random color as foreground in a white background.
My question is how do I generate an indefinitely numbered set of colors where there are no colors that are almost the same. e.g.: #D964D9 & #FF3EFF ?
It might be better to use HSV coordinates. If you don't need white or black, you can set S and V to their maximum values, and generate H values that are not too close to each other (mod 360 degrees). Then convert to RGB.
There are several methods which spring to mind:
Set up a array of n standard colors and interchange them randomly to produce the desired "random" colors.
Fill an array of n colors; generate a random color and check if there is something "close" already in the array. If so, choose another random color.
Select each color as a deterministic sequence, like a simple hash value, designed to not produce duplicate values. Grey code springs to mind.
Your algorithm could randomly generate RGB colors (as it's doing now) however you could for example verify that the two R's are sufficiently different before accepting the color choice. The algorithm could repeat that step (say up to 4...10...N times) for a given R, G and/or B.
while ( (R1 > $max_r/2) && (R2 > $max_r/2) ) {
// Both are in the upper half of range, get a new random value for R1.
}
Other possibilities:
Repeat for the lower half of range
Further sub-divide ranges (into 1/3's or 1/4's)
Repeat for G and B tones

Resources