Lua - xterm 256 colors gradient scripting - colors

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.

Related

How to build a Gray-code generator in Picat?

Encouraged by the knowledge I've gained from the answer to my previous post, I aim to generate Gray-codes of given length. The procedure hamming seems to work correctly, however, the Picat system finds no solution. Where's the mistake here?
import cp.
main => gray(2).
gray(CodeLen) =>
CodeNr is 2**CodeLen,
Codes = new_array(CodeNr, CodeLen),
Codes :: 0..1,
foreach(CodeNr1 in 1..CodeNr)
CodeNr2 = cond(CodeNr1 == CodeNr, 1, CodeNr1 + 1),
hamming(Codes[CodeNr1], Codes[CodeNr2], 0, H),
H #= 1
% the Hamming distance between 2 consecutive codes is 1
end,
solve(Codes),
printf("%w\n", Codes).
hamming([], [], A, H) ?=> H #= A.
hamming([H1|T1], [H2|T2], A, H) ?=>
H1 #!= H2,
A1 #= A + 1,
hamming(T1, T2, A1, H).
hamming([H1|T1], [H2|T2], A, H) ?=>
H1 #= H2,
A1 #= A + 0,
hamming(T1, T2, A1, H).
The reason that the model don't print anything is that you are using list constructs ([H|T]) on the array matrix Code which is not allowed. You have to convert the rows of the matrix (which are arrays) to lists. This can be done in two ways:
Convert the array matrix Code matrix to a list matrix with array_matrix_to_list_matrix() (requires that the util package is loaded):
import util.
% ....
gray(CodeLen) =>
CodeNr is 2**CodeLen,
Codes = new_array(CodeNr, CodeLen).array_matrix_to_list_matrix, % <--
Codes :: 0..1,
% ....
Convert the array parameters in the call to hamming/4 to lists with theto_list() function. E.g.:
% ...
foreach(CodeNr1 in 1..CodeNr)
CodeNr2 = cond(CodeNr1 == CodeNr, 1, CodeNr1 + 1),
% hamming(Codes[CodeNr1], Codes[CodeNr2], 0, H), % Original
hamming(Codes[CodeNr1].to_list, Codes[CodeNr2].to_list, 0, H), % <---
H #= 1
% the Hamming distance between 2 consecutive codes is 1
end,
% ...
Update.
Here's a constraint model that solves the problem of generating different rows that was indicated in the comment. It uses a simpler version of hamming_distance by just counting the number of different bits with sum. Also, for symmetry, I require that the first and last row also have a Hamming distance of 1. (This was in the original code.)
To require different rows, the constraint to_num/3 is used to converts a number to digits in an array (given a base, here 2). These numbers (which must be distinct) are in the CodesNum list.
import cp,util.
main =>
go.
go ?=>
gray(5),
nl,
% fail,
nl.
go => true.
% First solution for N=2..10
go2 ?=>
foreach(N in 2..10)
println(n=N),
if time(gray(N)) then
true
else
println(nope)
end,
nl
end,
nl.
go2 => true.
gray(CodeLen) =>
CodeNr is 2**CodeLen,
println(codeNr=CodeNr),
Codes = new_array(CodeNr, CodeLen).array_matrix_to_list_matrix,
Codes :: 0..1,
CodesNum = new_list(CodeNr), % array -> integer
CodesNum :: 0..CodeNr,
foreach(CodeNr1 in 1..CodeNr)
to_num(Codes[CodeNr1],2,CodesNum[CodeNr1]),
CodeNr2 = cond(CodeNr1 == CodeNr, 1, CodeNr1 + 1),
hamming_distance(Codes[CodeNr1], Codes[CodeNr2], 1),
end,
% around the corner
% hamming_distance(Codes[1], Codes[CodeNr],1),
all_different(CodesNum),
CodesNum[1] #= 0, % symmetry breaking
Vars = CodesNum ++ Codes.vars,
solve($[ff,updown],Vars),
printf("%w\n", Codes),
println(codesNum=CodesNum),nl.
% Hamming distance of As and Bs
hamming_distance(As, Bs,Diff) =>
Diff #= sum([(A #!= B) : {A,B} in zip(As,Bs)]).
% Convert Num to/from a list of digits in List (base Base)
to_num(List, Base, Num) =>
Len = length(List),
Num #= sum([List[I]*Base**(Len-I) : I in 1..Len]).
to_num(List, Num) =>
to_num(List, 10, Num).
It solves N=4 in 0s:
n = 4
codeNr = 16
[[0,0,0,0],[1,0,0,0],[1,1,0,0],[1,1,1,0],[1,1,1,1],[1,1,0,1],[1,0,0,1],[1,0,1,1],[1,0,1,0],[0,0,1,0],[0,1,1,0],[0,1,1,1],[0,0,1,1],[0,0,0,1],[0,1,0,1],[0,1,0,0]]
codesNum = [0,8,12,14,15,13,9,11,10,2,6,7,3,1,5,4]
CPU time 0.0 seconds.
The model solves N=2..7 (first solution) quite fast, but it struggles with N=8, and I don't have the time to test different search heuristics to make it faster.
Here's some another approach for solving the gray code but without constraint modelling and it's much faster: http://hakank.org/picat/gray_code.pi
Update2 Here's a much faster version of hamming/4. It use a reification (boolean) variable B to check if H1 and H2 are different and can then be used as the value to add to A0.
hamming2([], [], A, A).
hamming2([H1|T1], [H2|T2], A0, H) :-
B :: 0..1,
H1 #!= H2 #<=> B #= 1,
A1 #= A0 + B,
hamming2(T1, T2, A1, H).

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.

Distance to a straight line in standard form

For a 3D straight line expressed in the standard form
a1*x + b1*y + c1*z + d1 = 0
a2*x + b2*y + c2*z + d2 = 0
and a given point x0,y0,z0
what is the distance from the point to the straight line?
Distance from point P0 to parametric line L(t) = Base + t * Dir is
Dist = Length(CrossProduct(Dir, P0 - Base)) / Length(Dir)
To find direction vector:
Dir = CrossProduct((a1,b1,c1), (a2,b2,c2))
To get some arbitrary base point, solve equation system with 2 equations and three unknowns (find arbitrary solution):
a1*x + b1*y + c1*z + d1 = 0
a2*x + b2*y + c2*z + d2 = 0
Check minors consisting of a and b, a and c, b and c coefficients. When minor is non-zero, corresponding variable might be taken as free one. For example, if a1 * b2 - b1 * a2 <> 0, choose variable z as free - make it zero or another value and solve system for two unknowns x and y.
(I omitted extra cases of parallel or coinciding planes)

finding the closest web safe color if I have a palette

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.

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