finding the closest web safe color if I have a palette - colors

How do I take r,g,b values and compare them to a websafe color palette to find the best match for the r,g,b value?
There's this one:
What is the best algorithm for finding the closest color in an array to another color?
But I don't think it's what I need. I just need to compare an r,g,b with a websafe color and find out if the websafe color is the best choice.
Edit1: deleted
Edit2:
This is what I have so far.
local r, g, b = HSV2RGB(h, s, v)
local dither = copy(WEB_SAFE)
local lmod
for i, v in ipairs(dither) do
local r2, g2, b2 = Color2RGBA(v)
local hh, ss, vv = RGB2HSV(r2, g2, b2)
local a = hh - h
local b = ss - s
local c = vv - v
local mod = a*a + b*b + c*c
if not lmod or mod < lmod then
lmod = mod
r, g, b = r2, g2,b2
end
end
texture:SetBackgroundColor(r, g, b)
Edit 3:
Is this what it's supposed to look like?
h=1 through 360 at 5 pt steps, s=1 through 100, v = 89

I'm not sure that HSV is the best color-space to perform the calculation in -- also it's a cylinder, not a cube, so your distance formula (which would work fine in RGB) would produce inappropriate results for HSV.
In any case, the Web safe palette is itself a simple RGB color cube, with six possible values (0-5) for each component. You shouldn't even need to do something as complex as iterating to derive a Web safe color from an input color: just determine the appropriate Web safe value for each color component (R, G, B) independently.
On the rash assumption that your RGB component values range from 0..255:
local max_color_component_value = 255
local quantum = max_color_component_value / 5
r = quantum * math.floor((r + (quantum / 2)) / quantum)
g = quantum * math.floor((g + (quantum / 2)) / quantum)
b = quantum * math.floor((b + (quantum / 2)) / quantum)
If some other range is used, adjust max_color_component_value appropriately.

Related

Change lightness of a color so that a minimum contrast ratio is met

Given the following inputs:
color: Black (rgb(0,0,0))
contrastRatio: 7.0
I would like to modify the lightness of color so that a contrast ratio of contrastRatio exists between the new brightened/darkened color and the original.
In the above case, a lightness of 0.585 should be set on black in order to meet the 7.0 contrast ratio.
Help much appreciated!
You use formula 1 of https://w3.org/TR/WCAG20-TECHS/G18.html
L = 0.2126 * R + 0.7152 * G + 0.0722 * B where R, G and B are defined as:
if R_sRGB <= 0.03928 then R = R_sRGB /12.92 else R = ((R_sRGB +0.055)/1.055) ^ 2.4
if G_sRGB <= 0.03928 then G = G_sRGB /12.92 else G = ((G_sRGB +0.055)/1.055) ^ 2.4
if B_sRGB <= 0.03928 then B = B_sRGB /12.92 else B = ((B_sRGB +0.055)/1.055) ^ 2.4
and R_sRGB, G_sRGB, and B_sRGB are defined as:
R_sRGB = R_8bit /255
G_sRGB = G_8bit /255
B_sRGB = B_8bit /255
The "^" character is the exponentiation operator.
so you get the R. No need to use the special case of very dark colours: it is wrong for our cases (and for most cases).
Then you divide R, G, B by 7.0 (really you can do better with the (L1 + 0.05) / (L2 + 0.05) formula (formula in point 3). This is simple maths.
And now you apply the inverse formula to get R_sRGB, G_sRGB, and B_sRGB. and then you multiply the 3 channels with 255. You can optimize calculations.
In this manner you have the new colour with higher contrast, but you keep hue and saturation.
I use divide, assuming you want darker colour, but you can do with multiply for brighter colour, and probably you should find a threshold where you can go down or up.
It is easy maths, just you should keep in mind you should multiply linear R, G, B with the same factor to keep hue/saturation constant, and to forget very dark exception on gamma (if you use it, you get wrong colour: it is just a trick for displays, but it should usually not be used, and never in this case, where you apply again the inverse transformation.

Boundary enclosing a given set of points

I am having a bit of a problem with an algorithm that I am currently using. I wanted it to make a boundary.
Here is an example of the current behavior:
Here is an MSPaint example of wanted behavior:
Current code of Convex Hull in C#:https://hastebin.com/dudejesuja.cs
So here are my questions:
1) Is this even possible?
R: Yes
2) Is this even called Convex Hull? (I don't think so)
R: Nope it is called boundary, link: https://www.mathworks.com/help/matlab/ref/boundary.html
3) Will this be less performance friendly than a conventional convex hull?
R: Well as far as I researched it should be the same performance
4) Example of this algorithm in pseudo code or something similar?
R: Not answered yet or I didn't find a solution yet
Here is some Python code that computes the alpha-shape (concave hull) and keeps only the outer boundary. This is probably what matlab's boundary does inside.
from scipy.spatial import Delaunay
import numpy as np
def alpha_shape(points, alpha, only_outer=True):
"""
Compute the alpha shape (concave hull) of a set of points.
:param points: np.array of shape (n,2) points.
:param alpha: alpha value.
:param only_outer: boolean value to specify if we keep only the outer border
or also inner edges.
:return: set of (i,j) pairs representing edges of the alpha-shape. (i,j) are
the indices in the points array.
"""
assert points.shape[0] > 3, "Need at least four points"
def add_edge(edges, i, j):
"""
Add an edge between the i-th and j-th points,
if not in the list already
"""
if (i, j) in edges or (j, i) in edges:
# already added
assert (j, i) in edges, "Can't go twice over same directed edge right?"
if only_outer:
# if both neighboring triangles are in shape, it's not a boundary edge
edges.remove((j, i))
return
edges.add((i, j))
tri = Delaunay(points)
edges = set()
# Loop over triangles:
# ia, ib, ic = indices of corner points of the triangle
for ia, ib, ic in tri.vertices:
pa = points[ia]
pb = points[ib]
pc = points[ic]
# Computing radius of triangle circumcircle
# www.mathalino.com/reviewer/derivation-of-formulas/derivation-of-formula-for-radius-of-circumcircle
a = np.sqrt((pa[0] - pb[0]) ** 2 + (pa[1] - pb[1]) ** 2)
b = np.sqrt((pb[0] - pc[0]) ** 2 + (pb[1] - pc[1]) ** 2)
c = np.sqrt((pc[0] - pa[0]) ** 2 + (pc[1] - pa[1]) ** 2)
s = (a + b + c) / 2.0
area = np.sqrt(s * (s - a) * (s - b) * (s - c))
circum_r = a * b * c / (4.0 * area)
if circum_r < alpha:
add_edge(edges, ia, ib)
add_edge(edges, ib, ic)
add_edge(edges, ic, ia)
return edges
If you run it with the following test code you will get this figure, which looks like what you need:
from matplotlib.pyplot import *
# Constructing the input point data
np.random.seed(0)
x = 3.0 * np.random.rand(2000)
y = 2.0 * np.random.rand(2000) - 1.0
inside = ((x ** 2 + y ** 2 > 1.0) & ((x - 3) ** 2 + y ** 2 > 1.0)
points = np.vstack([x[inside], y[inside]]).T
# Computing the alpha shape
edges = alpha_shape(points, alpha=0.25, only_outer=True)
# Plotting the output
figure()
axis('equal')
plot(points[:, 0], points[:, 1], '.')
for i, j in edges:
plot(points[[i, j], 0], points[[i, j], 1])
show()
EDIT: Following a request in a comment, here is some code that "stitches" the output edge set into sequences of consecutive edges.
def find_edges_with(i, edge_set):
i_first = [j for (x,j) in edge_set if x==i]
i_second = [j for (j,x) in edge_set if x==i]
return i_first,i_second
def stitch_boundaries(edges):
edge_set = edges.copy()
boundary_lst = []
while len(edge_set) > 0:
boundary = []
edge0 = edge_set.pop()
boundary.append(edge0)
last_edge = edge0
while len(edge_set) > 0:
i,j = last_edge
j_first, j_second = find_edges_with(j, edge_set)
if j_first:
edge_set.remove((j, j_first[0]))
edge_with_j = (j, j_first[0])
boundary.append(edge_with_j)
last_edge = edge_with_j
elif j_second:
edge_set.remove((j_second[0], j))
edge_with_j = (j, j_second[0]) # flip edge rep
boundary.append(edge_with_j)
last_edge = edge_with_j
if edge0[0] == last_edge[1]:
break
boundary_lst.append(boundary)
return boundary_lst
You can then go over the list of boundary lists and append the points corresponding to the first index in each edge to get a boundary polygon.
I would use a different approach to solve this problem. Since we are working with a 2-D set of points, it is straightforward to compute the bounding rectangle of the points’ region. Then I would divide this rectangle into “cells” by horizontal and vertical lines, and for each cell simply count the number of pixels located within its bounds. Since each cell can have only 4 adjacent cells (adjacent by cell sides), then the boundary cells would be the ones that have at least one empty adjacent cell or have a cell side located at the bounding rectangle boundary. Then the boundary would be constructed along boundary cell sides. The boundary would look like a “staircase”, but choosing a smaller cell size would improve the result. As a matter of fact, the cell size should be determined experimentally; it could not be too small, otherwise inside the region may appear empty cells. An average distance between the points could be used as a lower boundary of the cell size.
Consider using an Alpha Shape, sometimes called a Concave Hull. https://en.wikipedia.org/wiki/Alpha_shape
It can be built from the Delaunay triangulation, in time O(N log N).
As pointed out by most previous experts, this might not be a convex hull but a concave hull, or an Alpha Shape in other words. Iddo provides a clean Python code to acquire this shape. However, you can also directly utilize some existing packages to realize that, perhaps with a faster speed and less computational memory if you are working with a large number of point clouds.
[1] Alpha Shape Toolbox: a toolbox for generating n-dimensional alpha shapes.
https://plotly.com/python/v3/alpha-shapes/
[2] Plotly: It can can generate a Mesh3d object, that depending on a key-value can be the convex hull of that set, its Delaunay triangulation, or an alpha set.
https://plotly.com/python/v3/alpha-shapes/
Here is the JavaScript code that builds concave hull: https://github.com/AndriiHeonia/hull Probably you can port it to C#.
One idea is creating triangles, a mesh, using the point cloud, perhaps through Delanuay triangulation,
and filling those triangles with a color then run level set, or active contour segmentation which will find the outer boundary of the shape whose color is now different then the outside "background" color.
https://xphilipp.developpez.com/contribuez/SnakeAnimation.gif
The animation above did not go all the way but many such algorithms can be configured to do that.
Note: The triangulation alg has to be tuned so that it doesn't merely create a convex hull - for example removing triangles with too large angles and sides from the delanuay result. A prelim code could look like
from scipy.spatial import Delaunay
points = np.array([[13.43, 12.89], [14.44, 13.86], [13.67, 15.87], [13.39, 14.95],\
[12.66, 13.86], [10.93, 14.24], [11.69, 15.16], [13.06, 16.24], [11.29, 16.35],\
[10.28, 17.33], [10.12, 15.49], [9.03, 13.76], [10.12, 14.08], [9.07, 15.87], \
[9.6, 16.68], [7.18, 16.19], [7.62, 14.95], [8.39, 16.79], [8.59, 14.51], \
[8.1, 13.43], [6.57, 11.59], [7.66, 11.97], [6.94, 13.86], [6.53, 14.84], \
[5.48, 12.84], [6.57, 12.56], [5.6, 11.27], [6.29, 10.08], [7.46, 10.45], \
[7.78, 7.21], [7.34, 8.72], [6.53, 8.29], [5.85, 8.83], [5.56, 10.24], [5.32, 7.8], \
[5.08, 9.86], [6.01, 5.75], [6.41, 7.48], [8.19, 5.69], [8.23, 4.72], [6.85, 6.34], \
[7.02, 4.07], [9.4, 3.2], [9.31, 4.99], [7.86, 3.15], [10.73, 2.82], [10.32, 4.88], \
[9.72, 1.58], [11.85, 5.15], [12.46, 3.47], [12.18, 1.58], [11.49, 3.69], \
[13.1, 4.99], [13.63, 2.61]])
tri = Delaunay(points,furthest_site=False)
res = []
for t in tri.simplices:
A,B,C = points[t[0]],points[t[1]],points[t[2]]
e1 = B-A; e2 = C-A
num = np.dot(e1, e2)
n1 = np.linalg.norm(e1); n2 = np.linalg.norm(e2)
denom = n1 * n2
d1 = np.rad2deg(np.arccos(num/denom))
e1 = C-B; e2 = A-B
num = np.dot(e1, e2)
denom = np.linalg.norm(e1) * np.linalg.norm(e2)
d2 = np.rad2deg(np.arccos(num/denom))
d3 = 180-d1-d2
res.append([n1,n2,d1,d2,d3])
res = np.array(res)
m = res[:,[0,1]].mean()*res[:,[0,1]].std()
mask = np.any(res[:,[2,3,4]] > 110) & (res[:,0] < m) & (res[:,1] < m )
plt.triplot(points[:,0], points[:,1], tri.simplices[mask])
Then fill with color and segment.

Lua - xterm 256 colors gradient scripting

This has been bothering me for a bit, and I'm not sure there is an answer out there. I know there are modules such as Love2D that accomplish gradients, which I'm guessing uses RGB coloring. However, I'm needing to find something quite similar using xterm 256 colors, but I cannot seem to find a gradient map anywhere to assist with this.
My guess is that I'll have to create a "nearest to RGB color" and create a gradient from that, matching the corresponding RBG to the nearest xterm match, but to be quite honest, I don't even know where to begin with this. I know there's a "convert xterm to RGB hex" script in Python (located here), but as I don't know Python, I don't know how to convert that to Lua.
Ultimately, what I want to do is be able to turn text into a rainbow gradient, more or less. I currently have a function to return xterm colors, but it's completely random, and the output can be a bit harsh to read. Here's what I have for that code. The #x stands for "convert to xterm color", and it is followed by a three digit code (001 to 255), followed by the text.
function rainbow(text)
local rtext = ""
local xcolor = 1
local sbyte = 1
for i = 1, #text do
math.randomseed(os.time() * xcolor * sbyte)
sbyte = string.byte(i)
xcolor = math.random(1, 255)
rtext = rtext .. "#x" .. string.rep("0", 3 - string.len(xcolor)) .. xcolor .. text:sub(i,i)
end
return rtext
end
So, for example, print(rainbow("Test")) would result in:
#x211T#x069e#x154s#x177t
Obviously, this is not a gradient, and is not what I want to end up with. Is what I want possible, or is it a lost cause?
Edit
I know the limitiations of 256 colors, and I know there's not a whole lot of wiggle room. As was pointed out in the comment, there'd be a lot of the same color matching, so I'd get a string of the same colors. That's fine with me, really. No matter how many actual transitions it makes, I'd like for it to closely simulate a gradient.
What would be nice is if I were able to at least create color groups properly without having to poll the charts. What I may wind up having to do, I guess, is create a table of "compatible colors schemes" and work with that, unless someone has a better idea.
Define nearest_term256_color_index function:
local abs, min, max, floor = math.abs, math.min, math.max, math.floor
local levels = {[0] = 0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff}
local function index_0_5(value) -- value = color component 0..255
return floor(max((value - 35) / 40, value / 58))
end
local function nearest_16_231(r, g, b) -- r, g, b = 0..255
-- returns color_index_from_16_to_231, appr_r, appr_g, appr_b
r, g, b = index_0_5(r), index_0_5(g), index_0_5(b)
return 16 + 36 * r + 6 * g + b, levels[r], levels[g], levels[b]
end
local function nearest_232_255(r, g, b) -- r, g, b = 0..255
local gray = (3 * r + 10 * g + b) / 14
-- this is a rational approximation for well-known formula
-- gray = 0.2126 * r + 0.7152 * g + 0.0722 * b
local index = min(23, max(0, floor((gray - 3) / 10)))
gray = 8 + index * 10
return 232 + index, gray, gray, gray
end
local function color_distance(r1, g1, b1, r2, g2, b2)
return abs(r1 - r2) + abs(g1 - g2) + abs(b1 - b2)
end
local function nearest_term256_color_index(r, g, b) -- r, g, b = 0..255
local idx1, r1, g1, b1 = nearest_16_231(r, g, b)
local idx2, r2, g2, b2 = nearest_232_255(r, g, b)
local dist1 = color_distance(r, g, b, r1, g1, b1)
local dist2 = color_distance(r, g, b, r2, g2, b2)
return dist1 < dist2 and idx1 or idx2
end
Define generate_gradient function which inserts #x... in your text:
local unpack, tonumber = table.unpack or unpack, tonumber
local function convert_color_to_table(rrggbb)
if type(rrggbb) == "string" then
local r, g, b = rrggbb:match"(%x%x)(%x%x)(%x%x)"
return {tonumber(r, 16), tonumber(g, 16), tonumber(b, 16)}
else
return rrggbb
end
end
local function round(x)
return floor(x + 0.5)
end
local function generate_gradient(text, first_color, last_color)
local r, g, b = unpack(convert_color_to_table(first_color))
local dr, dg, db = unpack(convert_color_to_table(last_color))
local char_pattern = "[^\128-\191][\128-\191]*"
local n = max(1, select(2, text:gsub(char_pattern, "")) - 1)
dr, dg, db = (dr - r)/n, (dg - g)/n, (db - b)/n
local result = ""
for c in text:gmatch(char_pattern) do
result = result..("#x%03d"):format(nearest_term256_color_index(
round(r), round(g), round(b)))..c
r, g, b = r + dr, g + dg, b + db
end
return result
end
Test it inside terminal:
local function print_with_colors(str)
print(
str:gsub("#x(%d%d%d)",
function(color_idx)
return "\27[38;5;"..color_idx.."m"
end)
.."\27[0m"
)
end
local str = "Gradient"
local blue, red = {0, 0, 255}, "#FF0000"
str = generate_gradient(str, blue, red) -- gradient from blue to red
print(str)
print_with_colors(str)
rainbow() is not a gradient, it would be a chain of several gradients.
I am done with this. Just using ANSI 256 color escape code pattern.
-- ref ANSI 256 color = https://robotmoon.com/256-colors/
str = '\27[38;5;34mG\27[38;5;35mr\27[38;5;36ma\27[38;5;37md\27[38;5;38mi\27[38;5;39me\27[38;5;50mn\27[38;5;51mt\27'
if f then f.Destroy() end
f=createForm()
pb=createPaintBox(f)
pb.Canvas.Font.Size=52
pb.Align='alClient'
local rect={}
rect.Left=0
rect.Top=0
rect.Right=pb.Width
rect.Bottom=pb.Height
pb.Repaint()
pb.Canvas.Font.Style='fsBold'
pb.Canvas.Font.Name='Trebuchet MS'
pb.Canvas.Font.Color = 0
pb.Canvas.textRect(rect,16,54,'Gradient')
pb.Canvas.textRect(rect,12,50,str)
Thank you #Egor Skriptunoff for the inspirited.

find point where barycentric weights have a specific value

I have triangle: a, b, c. Each vertex has a value: va, vb, vc. In my software the user drags point p around inside and outside of this triangle. I use barycentric coordinates to determine the value vp at p based on va, vb, and vc. So far, so good.
Now I want to limit p so that vp is within range min and max. If a user chooses p where vp is < min or > max, how can I find the point closest to p where vp is equal to min or max, respectively?
Edit: Here is an example where I test each point. Light gray is within min/max. How can I find the equations of the lines that make up the min/max boundary?
a = 200, 180
b = 300, 220
c = 300, 300
va = 1
vb = 1.4
vc = 3.2
min = 0.5
max = 3.5
Edit: FWIW, so far first I get the barycentric coordinates v,w for p using the triangle vertices a, b, c (standard stuff I think, but looks like this). Then to get vp:
u = 1 - w - v
vp = va * u + vb * w + vc * v
That is all fine. My trouble is that I need the line equations for min/max so I can choose a new position for p when vp is out of range. The new position for p is the point closest to p on the min or max line.
Note that p is an XY coordinate and vp is a value for that coordinate determined by the triangle and the values at each vertex. min and max are also values. The two line equations I need will give me XY coordinates for which the values determined by the triangle are min or max.
It doesn't matter if barycentric coordinates are used in the solution.
The trick is to use the ratio of value to cartesian distance to extend each triangle edge until it hits min or max. Easier to see with a pic:
The cyan lines show how the triangle edges are extended, the green Xs are points on the min or max lines. With just 2 of these points we know the slope if the line. The yellow lines show connecting the Xs aligns with the light gray.
The math works like this, first get the value distance between vb and vc:
valueDistBtoC = vc - vb
Then get the cartesian distance from b to c:
cartesianDistBtoC = b.distance(c)
Then get the value distance from b to max:
valueDistBtoMax = max - vb
Now we can cross multiply to get the cartesian distance from b to max:
cartesianDistBtoMax = (valueDistBtoMax * cartesianDistBtoC) / valueDistBtoC
Do the same for min and also for a,b and c,a. The 6 points are enough to restrict the position of p.
Consider your triangle to actually be a 3D triangle, with points (ax,ay,va), (bx,by,vb), and (cx,cy,vc). These three points define a plane, containing all the possible p,vp triplets obtainable through barycentric interpolation.
Now think of your constraints as two other planes, at z>=max and z<=min. Each of these planes intersects your triangle's plane along an infinite line; the infinite beam between them, projected back down onto the xy plane, represents the area of points which satisfy the constraints. Once you have the lines (projected down), you can just find which (if either) is violated by a particular point, and move it onto that constraint (along a vector which is perpendicular to the constraint).
Now I'm not sure about your hexagon, though. That's not the shape I would expect.
Mathematically speaking the problem is simply a change of coordinates. The more difficult part is finding a good notation for the quantities involved.
You have two systems of coordinates: (x,y) are the cartesian coordinates of your display and (v,w) are the baricentric coordinates with respect to the vectors (c-a),(b-a) which determine another (non orthogonal) system.
What you need is to find the equation of the two lines in the (x,y) system, then it will be easy to project the point p on these lines.
To achieve this you could explicitly find the matrix to pass from (x,y) coordinates to (v,w) coordinates and back. The function you are using toBaryCoords makes this computation to find the coordinates (v,w) from (x,y) and we can reuse that function.
We want to find the coefficients of the transformation from world coordinates (x,y) to barycentric coordinates (v,w). It must be in the form
v = O_v + x_v * x + y_v * y
w = O_w + x_w * x + y_w * y
i.e.
(v,w) = (O_v,O_w) + (x_v,y_y) * (x,y)
and you can determine (O_v,O_w) by computing toBaryCoord(0,0), then find (x_v,x_w) by computing the coordinates of (1,0) and find (y_v,y_w)=toBaryCoord(1,0) - (O_v,O_w) and then find (y_v,y_w) by computing (y_v,y_w) = toBaryCoord(0,1)-(O_v,O_w).
This computation requires calling toBaryCoord three times, but actually the coefficients are computed inside that routine every time, so you could modify it to compute at once all six values.
The value of your function vp can be computed as follows. I will use f instead of v because we are using v for a baricenter coordinate. Hence in the following I mean f(x,y) = vp, fa = va, fb = vb, fc = vc.
You have:
f(v,w) = fa + (fb-fa)*v + (fc-fa)*w
i.e.
f(x,y) = fa + (fb-fa) (O_v + x_v * x + y_v * y) + (fc-fa) (O_w + x_w * x + y_w * y)
where (x,y) are the coordinates of your point p. You can check the validity of this equation by inserting the coordinates of the three vertices a, b, c and verify that you obtain the three values fa, fb and fc. Remember that the barycenter coordinates of a are (0,0) hence O_v + x_v * a_x + y_v * a_y = 0 and so on... (a_x and a_y are the x,y coordinates of the point a).
If you let
q = fa + (fb_fa)*O_v + (fc-fa)*O_w
fx = (fb-fa)*x_v + (fc-fa) * x_w
fy = (fb-fa)*y_v + (fc-fa) * y_w
you get
f(x,y) = q + fx*x + fy * y
Notice that q, fx and fy can be computed once from a,b,c,fa,fb,fc and you can reuse them if you only change the coordinates (x,y) of the point p.
Now if f(x,y)>max, you can easily project (x,y) on the line where max is achieved. The coordinates of the projection are:
(x',y') = (x,y) - [(x,y) * (fx,fy) - max + q]/[(fx,fy) * (fx,fy)] (fx,fy)
Now. You would like to have the code. Well here is some pseudo-code:
toBarycoord(Vector2(0,0),a,b,c,O);
toBarycoord(Vector2(1,0),a,b,c,X);
toBarycoord(Vector2(0,1),a,b,c,Y);
X.sub(O); // X = X - O
Y.sub(O); // Y = Y - O
V = Vector2(fb-fa,fc-fa);
q = fa + V.dot(O); // q = fa + V*O
N = Vector2(V.dot(X),V.dot(Y)); // N = (V*X,V*Y)
// p is the point to be considered
f = q + N.dot(p); // f = q + N*p
if (f > max) {
Vector2 tmp;
tmp.set(N);
tmp.multiply((N.dot(p) - max + q)/(N.dot(N))); // scalar multiplication
p.sub(tmp);
}
if (f < min) {
Vector2 tmp;
tmp.set(N);
tmp.multiply((N.dot(p) - min + q)/(N.dot(N))); // scalar multiplication
p.sum(tmp);
}
We think of the problem as follows: The three points are interpreted as a triangle floating in 3D space with the value being the Z-axis and the cartesian coordinates mapped to the X- and Y- axes respectively.
Then the question is to find the gradient of the plane that is defined by the three points. The lines where the plane intersects with the z = min and z = max planes are the lines you want to restrict your points to.
If you have found a point p where v(p) > max or v(p) < min we need to go in the direction of the steepest slope (the gradient) until v(p + k * g) = max or min respectively. g is the direction of the gradient and k is the factor we need to find. The coordinates you are looking for (in the cartesian coordinates) are the corresponding components of p + k * g.
In order to determine g we calculate the orthonormal vector that is perpendicular to the plane that is determined by the three points using the cross product:
// input: px, py, pz,
// output: p2x, p2y
// local variables
var v1x, v1y, v1z, v2x, v2y, v2z, nx, ny, nz, tp, k,
// two vectors pointing from b to a and c respectively
v1x = ax - bx;
v1y = ay - by;
v1z = az - bz;
v2x = cx - bx;
v2y = cy - by;
v2z = cz - bz;
// the cross poduct
nx = v2y * v1z - v2z * v1y;
ny = v2z * v1x - v2x * v1z;
nz = v2x * v1y - v2y * v1x;
// using the right triangle altitude theorem
// we can calculate the vector that is perpendicular to n
// in our triangle we are looking for q where p is nz, and h is sqrt(nx*nx+ny*ny)
// the theorem says p*q = h^2 so p = h^2 / q - we use tp to disambiguate with the point p - we need to negate the value as it points into the opposite Z direction
tp = -(nx*nx + ny*ny) / nz;
// now our vector g = (nx, ny, tp) points into the direction of the steepest slope
// and thus is perpendicular to the bounding lines
// given a point p (px, py, pz) we can now calculate the nearest point p2 (p2x, p2y, p2z) where min <= v(p2z) <= max
if (pz > max){
// find k
k = (max - pz) / tp;
p2x = px + k * nx;
p2y = py + k * ny;
// proof: p2z = v = pz + k * tp = pz + ((max - pz) / tp) * tp = pz + max - pz = max
} else if (pz < min){
// find k
k = (min - pz) / tp;
p2x = px + k * nx;
p2y = py + k * ny;
} else {
// already fits
p2x = px;
p2y = py;
}
Note that obviously if the triangle is vertically oriented (in 2D it's not a triangle anymore actually), nz becomes zero and tp cannot be calculated. That's because there are no more two lines where the value is min or max respectively. For this case you will have to choose another value on the remaining line or point.

Programmatically darken a Hex colour [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 6 years ago.
Improve this question
What's the easiest way to programmatically darken a hex colour?
If you're not bothered about too much control, and just want a generally darker version of a colour, then:
col = (col & 0xfefefe) >> 1;
Is a nice quick way to halve a colour value (assuming it's packed as a byte per channel, obviously).
In the same way brighter would be:
col = (col & 0x7f7f7f) << 1;
Convert hex color into integer RBG components:
#FF6600 = rbg(255, 102, 0)
If you want to make it darker by 5%, then simply reduce all integer values by 5%:
255 - 5% = 242
102 - 5% = 96
0 - 5% = 0
= rbg(242, 96, 0)
Convert back to hex color
= #F26000
A function implemented in javascript:
// credits: richard maloney 2006
function getTintedColor(color, v) {
if (color.length >6) { color= color.substring(1,color.length)}
var rgb = parseInt(color, 16);
var r = Math.abs(((rgb >> 16) & 0xFF)+v); if (r>255) r=r-(r-255);
var g = Math.abs(((rgb >> 8) & 0xFF)+v); if (g>255) g=g-(g-255);
var b = Math.abs((rgb & 0xFF)+v); if (b>255) b=b-(b-255);
r = Number(r < 0 || isNaN(r)) ? 0 : ((r > 255) ? 255 : r).toString(16);
if (r.length == 1) r = '0' + r;
g = Number(g < 0 || isNaN(g)) ? 0 : ((g > 255) ? 255 : g).toString(16);
if (g.length == 1) g = '0' + g;
b = Number(b < 0 || isNaN(b)) ? 0 : ((b > 255) ? 255 : b).toString(16);
if (b.length == 1) b = '0' + b;
return "#" + r + g + b;
}
Example:
> getTintedColor("ABCEDEF", 10)
> #c6f7f9
Well, I don't have any pseudocode for you, but a tip. If you want to darken a color and maintain its hue, you should convert that hex to HSB (hue, saturation, brightness) rather than RGB. This way, you can adjust the brightness and it will still look like the same color without hue shifting. You can then convert that HSB back to hex.
given arg darken_factor # a number from 0 to 1, 0=no change, 1=black
for each byte in rgb_value
byte = byte * (1 - darken_factor)
I pieced together a nice two-liner function for this:
Programmatically Lighten or Darken a hex color (or rgb, and blend colors)
shadeColor2(hexcolor,-0.05) for 5% darker
shadeColor2(hexcolor,-0.25) for 25% darker
Use positives for lightening.
Split the hex color into its RGB components.
Convert each of these components into an integer value.
Multiply that integer by a fraction, such as 0.5, making sure the result is also integer.
Alternatively, subtract a set amount from that integer, being sure not to go below 0.
Convert the result back to hex.
Concatenate these values in RGB order, and use.
RGB colors (in hexadecimal RGB notation) get darker or lighter by adjusting shade, key, lightness, or brightness. See the playground: colorizer.org
Option 1. Translate R, G, B values to darken shade
This one is simple, but easy to mess up. Here is subtracting 16 points off the (0,255) scale from each value:
myHex = 0x8c36a9;
darkerHex = myHex - 0x101010;
# 0x7c2699;
The hex will underflow if any of the R,G,B values are 0x0f or lower. Something like this would fix that.
myHex = 0x87f609;
darkenBy = 0x10;
floor = 0x0;
darkerHex = (max((myHex >> 16) - darkenBy, floor) << 16) + \
(max(((myHex & 0xff00) >> 8) - darkenBy, floor) << 8) + \
max(((myHex & 0xff) - darkenBy), floor);
# 0x77e600
# substitute `ceiling=0xff;` and `min((myHex ...) + lightenBy, ceiling)` for lightening
Option 2. Scale R, G, B values to increase black
In the CMYK model, key (black) is 1 - max of R, G, B values on (0,1) scale.
This one is simple enough that you can get good results without too much code. You're rescaling the distribution of R, G, B values by a single scaling factor.
Express the scaling factor as 2-digit hex (so 50% would be .5*0x100 or 0x80, 1/16th is 0x10 and 10% rounds down to 0x19 ).
# Assumes integer division ... looking at you python3 >:(
myHex = 0x8c36a9;
keyFactor = 0x10; # Lighten or darken by 6.25%
R = myHex >> 16; # 0x8c
G = (myHex & 0xff00) >> 8; # 0x36
B = myHex & 0xff; # 0xa9
darkerHex = ((R-R*keyFactor/0x100) << 16) + # Darker R
((G-G*keyFactor/0x100) << 8) + # Darker G
(B-B*keyFactor/0x100); # Darker B
# 0x84339f
# substitute `(X+keyFactor-X*keyFactor/0x100)` for lightening
# 0x9443af
Option 3. Reduce Lightness or Brightness at constant hue
In the HSL representation of RGB, lightness is the midpoint between min and max of R, G, B values. For HSV, brightness is the max of R, G, B values.
Consider using your language's built-in or external RGB/HEX to HSL/HSV converter. Then adjust your L/V values and convert back to RGB/HSL. You can do the conversion by hand, as in #1 & #2, but the implementation may not save you any time over an existing converter (see links for the maths).
You should consider darken the color in L*a*b* color space. Here's an example in JavaScript using chroma.js:
chroma.hex("#FCFC00").darker(10).hex() // "#dde000"
A hex colour such as #FCFCFC consists of three pairs representing RGB. The second part of each pair can be reduced to darken any colour without altering the colour considerably.
eg. to darken #FCFCFC, lower the values of C to give #F0F0F0
Reducing the first part of each pair by a small amount will also darken the colour, but you will start to affect the colour more (eg. turning a green to a blue).

Resources