Is there a way to programmatically darken the color given RGB values?
library(colorspace)
cols <- c("#CDE4F3","#E7F3D3","#F7F0C7","#EFCFE5","#D0D1E7")
cols1 <- readhex(file = textConnection(paste(cols, collapse = "\n")),
class = "RGB")
#transform to hue/lightness/saturation colorspace
cols1 <- as(cols1, "HLS")
cols2 <- cols1
#additive decrease of lightness
cols1@coords[, "L"] <- pmax(0, cols1@coords[, "L"] - 0.3)
#multiplicative decrease of lightness
cols2@coords[, "L"] <- cols2@coords[, "L"] * 0.75
#going via rgb seems to work better
cols1 <- as(cols1, "RGB")
cols1 <- hex(cols1)
cols2 <- as(cols2, "RGB")
cols2 <- hex(cols2)
plot(x = seq_along(cols), y = rep(1, length(cols)),
col = cols, pch = 15, ylim = c(0, 4.5), cex = 5,
xlab = "", ylab = "")
points(x = seq_along(cols), y = rep(2, length(cols)),
col = cols1, pch = 16, cex = 5)
points(x = seq_along(cols), y = rep(3, length(cols)),
col = cols2, pch = 17, cex = 5)
legend("top",legend = c("original", "additive", "multipl."),
pch = 15:17, ncol = 3)
How can I darken/lighten a RGB color
Something like this maybe:
def interpolate(color_a, color_b, t):
# 'color_a' and 'color_b' are RGB tuples
# 't' is a value between 0.0 and 1.0
# this is a naive interpolation
return tuple(int(a + (b - a) * t) for a, b in zip(color_a, color_b))
def main():
color_a = (175, 250, 255)
color_b = (0, 0, 0)
number_of_steps = 10
colors = [interpolate(color_a, color_b, t/number_of_steps) for t in range(number_of_steps+1)]
for color in colors:
print(color)
return 0
if __name__ == "__main__":
import sys
sys.exit(main())
Output:
(175, 250, 255)
(157, 225, 229)
(140, 200, 204)
(122, 175, 178)
(105, 150, 153)
(87, 125, 127)
(70, 100, 102)
(52, 75, 76)
(35, 50, 51)
(17, 25, 25)
(0, 0, 0)
Programmatically Lighten or Darken a hex color (or rgb, and blend colors)
Well, this answer has become its own beast. Many new versions, it was getting stupid long. Many thanks to all of the great many contributors to this answer. But, in order to keep it simple for the masses. I archived all the versions/history of this answer's evolution to my github. And started it over clean on StackOverflow here with the newest version. A special thanks goes out to Mike 'Pomax' Kamermans for this version. He gave me the new math.
This function (pSBC
) will take a HEX or RGB web color. pSBC
can shade it darker or lighter, or blend it with a second color, and can also pass it right thru but convert from Hex to RGB (Hex2RGB) or RGB to Hex (RGB2Hex). All without you even knowing what color format you are using.
This runs really fast, probably the fastest, especially considering its many features. It was a long time in the making. See the whole story on my github. If you want the absolutely smallest and fastest possible way to shade or blend, see the Micro Functions below and use one of the 2-liner speed demons. They are great for intense animations, but this version here is fast enough for most animations.
This function uses Log Blending or Linear Blending. However, it does NOT convert to HSL to properly lighten or darken a color. Therefore, results from this function will differ from those much larger and much slower functions that use HSL.
jsFiddle with pSBC
github > pSBC Wiki
Features:
- Auto-detects and accepts standard Hex colors in the form of strings. For example:
"#AA6622"
or"#bb551144"
. - Auto-detects and accepts standard RGB colors in the form of strings. For example:
"rgb(123,45,76)"
or"rgba(45,15,74,0.45)"
. - Shades colors to white or black by percentage.
- Blends colors together by percentage.
- Does Hex2RGB and RGB2Hex conversion at the same time, or solo.
- Accepts 3 digit (or 4 digit w/ alpha) HEX color codes, in the form #RGB (or #RGBA). It will expand them. For Example:
"#C41"
becomes"#CC4411"
. - Accepts and (Linear) blends alpha channels. If either the
c0
(from) color or thec1
(to) color has an alpha channel, then the returned color will have an alpha channel. If both colors have an alpha channel, then the returned color will be a linear blend of the two alpha channels using the percentage given (just as if it were a normal color channel). If only one of the two colors has an alpha channel, this alpha will just be passed thru to the returned color. This allows one to blend/shade a transparent color while maintaining the transparency level. Or, if the transparency levels should blend as well, make sure both colors have alphas. When shading, it will pass the alpha channel straight thru. If you want basic shading that also shades the alpha channel, then usergb(0,0,0,1)
orrgb(255,255,255,1)
as yourc1
(to) color (or their hex equivalents). For RGB colors, the returned color's alpha channel will be rounded to 3 decimal places. - RGB2Hex and Hex2RGB conversions are implicit when using blending. Regardless of the
c0
(from) color; the returned color will always be in the color format of thec1
(to) color, if one exists. If there is noc1
(to) color, then pass'c'
in as thec1
color and it will shade and convert whatever thec0
color is. If conversion only is desired, then pass0
in as the percentage (p
) as well. If thec1
color is omitted or a non-string
is passed in, it will not convert. - A secondary function is added to the global as well.
pSBCr
can be passed a Hex or RGB color and it returns an object containing this color information. Its in the form: {r: XXX, g: XXX, b: XXX, a: X.XXX}. Where.r
,.g
, and.b
have range 0 to 255. And when there is no alpha:.a
is -1. Otherwise:.a
has range 0.000 to 1.000. - For RGB output, it outputs
rgba()
overrgb()
when a color with an alpha channel was passed intoc0
(from) and/orc1
(to). - Minor Error Checking has been added. It's not perfect. It can still crash or create jibberish. But it will catch some stuff. Basically, if the structure is wrong in some ways or if the percentage is not a number or out of scope, it will return
null
. An example:pSBC(0.5,"salt") == null
, where as it thinks#salt
is a valid color. Delete the four lines which end withreturn null;
to remove this feature and make it faster and smaller. - Uses Log Blending. Pass
true
in forl
(the 4th parameter) to use Linear Blending.
Code:
// Version 4.0
const pSBC=(p,c0,c1,l)=>{
let r,g,b,P,f,t,h,i=parseInt,m=Math.round,a=typeof(c1)=="string";
if(typeof(p)!="number"||p<-1||p>1||typeof(c0)!="string"||(c0[0]!='r'&&c0[0]!='#')||(c1&&!a))return null;
if(!this.pSBCr)this.pSBCr=(d)=>{
let n=d.length,x={};
if(n>9){
[r,g,b,a]=d=d.split(","),n=d.length;
if(n<3||n>4)return null;
x.r=i(r[3]=="a"?r.slice(5):r.slice(4)),x.g=i(g),x.b=i(b),x.a=a?parseFloat(a):-1
}else{
if(n==8||n==6||n<4)return null;
if(n<6)d="#"+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]+(n>4?d[4]+d[4]:"");
d=i(d.slice(1),16);
if(n==9||n==5)x.r=d>>24&255,x.g=d>>16&255,x.b=d>>8&255,x.a=m((d&255)/0.255)/1000;
else x.r=d>>16,x.g=d>>8&255,x.b=d&255,x.a=-1
}return x};
h=c0.length>9,h=a?c1.length>9?true:c1=="c"?!h:false:h,f=this.pSBCr(c0),P=p<0,t=c1&&c1!="c"?this.pSBCr(c1):P?{r:0,g:0,b:0,a:-1}:{r:255,g:255,b:255,a:-1},p=P?p*-1:p,P=1-p;
if(!f||!t)return null;
if(l)r=m(P*f.r+p*t.r),g=m(P*f.g+p*t.g),b=m(P*f.b+p*t.b);
else r=m((P*f.r**2+p*t.r**2)**0.5),g=m((P*f.g**2+p*t.g**2)**0.5),b=m((P*f.b**2+p*t.b**2)**0.5);
a=f.a,t=t.a,f=a>=0||t>=0,a=f?a<0?t:t<0?a:a*P+t*p:0;
if(h)return"rgb"+(f?"a(":"(")+r+","+g+","+b+(f?","+m(a*1000)/1000:"")+")";
else return"#"+(4294967296+r*16777216+g*65536+b*256+(f?m(a*255):0)).toString(16).slice(1,f?undefined:-2)
}
Usage:
// Setup:
let color1 = "rgb(20,60,200)";
let color2 = "rgba(20,60,200,0.67423)";
let color3 = "#67DAF0";
let color4 = "#5567DAF0";
let color5 = "#F3A";
let color6 = "#F3A9";
let color7 = "rgb(200,60,20)";
let color8 = "rgba(200,60,20,0.98631)";
// Tests:
/*** Log Blending ***/
// Shade (Lighten or Darken)
pSBC ( 0.42, color1 ); // rgb(20,60,200) + [42% Lighter] => rgb(166,171,225)
pSBC ( -0.4, color5 ); // #F3A + [40% Darker] => #c62884
pSBC ( 0.42, color8 ); // rgba(200,60,20,0.98631) + [42% Lighter] => rgba(225,171,166,0.98631)
// Shade with Conversion (use "c" as your "to" color)
pSBC ( 0.42, color2, "c" ); // rgba(20,60,200,0.67423) + [42% Lighter] + [Convert] => #a6abe1ac
// RGB2Hex & Hex2RGB Conversion Only (set percentage to zero)
pSBC ( 0, color6, "c" ); // #F3A9 + [Convert] => rgba(255,51,170,0.6)
// Blending
pSBC ( -0.5, color2, color8 ); // rgba(20,60,200,0.67423) + rgba(200,60,20,0.98631) + [50% Blend] => rgba(142,60,142,0.83)
pSBC ( 0.7, color2, color7 ); // rgba(20,60,200,0.67423) + rgb(200,60,20) + [70% Blend] => rgba(168,60,111,0.67423)
pSBC ( 0.25, color3, color7 ); // #67DAF0 + rgb(200,60,20) + [25% Blend] => rgb(134,191,208)
pSBC ( 0.75, color7, color3 ); // rgb(200,60,20) + #67DAF0 + [75% Blend] => #86bfd0
/*** Linear Blending ***/
// Shade (Lighten or Darken)
pSBC ( 0.42, color1, false, true ); // rgb(20,60,200) + [42% Lighter] => rgb(119,142,223)
pSBC ( -0.4, color5, false, true ); // #F3A + [40% Darker] => #991f66
pSBC ( 0.42, color8, false, true ); // rgba(200,60,20,0.98631) + [42% Lighter] => rgba(223,142,119,0.98631)
// Shade with Conversion (use "c" as your "to" color)
pSBC ( 0.42, color2, "c", true ); // rgba(20,60,200,0.67423) + [42% Lighter] + [Convert] => #778edfac
// RGB2Hex & Hex2RGB Conversion Only (set percentage to zero)
pSBC ( 0, color6, "c", true ); // #F3A9 + [Convert] => rgba(255,51,170,0.6)
// Blending
pSBC ( -0.5, color2, color8, true ); // rgba(20,60,200,0.67423) + rgba(200,60,20,0.98631) + [50% Blend] => rgba(110,60,110,0.83)
pSBC ( 0.7, color2, color7, true ); // rgba(20,60,200,0.67423) + rgb(200,60,20) + [70% Blend] => rgba(146,60,74,0.67423)
pSBC ( 0.25, color3, color7, true ); // #67DAF0 + rgb(200,60,20) + [25% Blend] => rgb(127,179,185)
pSBC ( 0.75, color7, color3, true ); // rgb(200,60,20) + #67DAF0 + [75% Blend] => #7fb3b9
/*** Other Stuff ***/
// Error Checking
pSBC ( 0.42, "#FFBAA" ); // #FFBAA + [42% Lighter] => null (Invalid Input Color)
pSBC ( 42, color1, color5 ); // rgb(20,60,200) + #F3A + [4200% Blend] => null (Invalid Percentage Range)
pSBC ( 0.42, {} ); // [object Object] + [42% Lighter] => null (Strings Only for Color)
pSBC ( "42", color1 ); // rgb(20,60,200) + ["42"] => null (Numbers Only for Percentage)
pSBC ( 0.42, "salt" ); // salt + [42% Lighter] => null (A Little Salt is No Good...)
// Error Check Fails (Some Errors are not Caught)
pSBC ( 0.42, "#salt" ); // #salt + [42% Lighter] => #a5a5a500 (...and a Pound of Salt is Jibberish)
// Ripping
pSBCr ( color4 ); // #5567DAF0 + [Rip] => [object Object] => {'r':85,'g':103,'b':218,'a':0.941}
The picture below will help show the difference in the two blending methods:
Micro Functions
If you really want speed and size, you will have to use RGB not HEX. RGB is more straightforward and simple, HEX writes too slow and comes in too many flavors for a simple two-liner (IE. it could be a 3, 4, 6, or 8 digit HEX code). You will also need to sacrifice some features, no error checking, no HEX2RGB nor RGB2HEX. As well, you will need to choose a specific function (based on its function name below) for the color blending math, and if you want shading or blending. These functions do support alpha channels. And when both input colors have alphas it will Linear Blend them. If only one of the two colors has an alpha, it will pass it straight thru to the resulting color. Below are two liner functions that are incredibly fast and small:
const RGB_Linear_Blend=(p,c0,c1)=>{
var i=parseInt,r=Math.round,P=1-p,[a,b,c,d]=c0.split(","),[e,f,g,h]=c1.split(","),x=d||h,j=x?","+(!d?h:!h?d:r((parseFloat(d)*P+parseFloat(h)*p)*1000)/1000+")"):")";
return"rgb"+(x?"a(":"(")+r(i(a[3]=="a"?a.slice(5):a.slice(4))*P+i(e[3]=="a"?e.slice(5):e.slice(4))*p)+","+r(i(b)*P+i(f)*p)+","+r(i(c)*P+i(g)*p)+j;
}
const RGB_Linear_Shade=(p,c)=>{
var i=parseInt,r=Math.round,[a,b,c,d]=c.split(","),P=p<0,t=P?0:255*p,P=P?1+p:1-p;
return"rgb"+(d?"a(":"(")+r(i(a[3]=="a"?a.slice(5):a.slice(4))*P+t)+","+r(i(b)*P+t)+","+r(i(c)*P+t)+(d?","+d:")");
}
const RGB_Log_Blend=(p,c0,c1)=>{
var i=parseInt,r=Math.round,P=1-p,[a,b,c,d]=c0.split(","),[e,f,g,h]=c1.split(","),x=d||h,j=x?","+(!d?h:!h?d:r((parseFloat(d)*P+parseFloat(h)*p)*1000)/1000+")"):")";
return"rgb"+(x?"a(":"(")+r((P*i(a[3]=="a"?a.slice(5):a.slice(4))**2+p*i(e[3]=="a"?e.slice(5):e.slice(4))**2)**0.5)+","+r((P*i(b)**2+p*i(f)**2)**0.5)+","+r((P*i(c)**2+p*i(g)**2)**0.5)+j;
}
const RGB_Log_Shade=(p,c)=>{
var i=parseInt,r=Math.round,[a,b,c,d]=c.split(","),P=p<0,t=P?0:p*255**2,P=P?1+p:1-p;
return"rgb"+(d?"a(":"(")+r((P*i(a[3]=="a"?a.slice(5):a.slice(4))**2+t)**0.5)+","+r((P*i(b)**2+t)**0.5)+","+r((P*i(c)**2+t)**0.5)+(d?","+d:")");
}
Want more info? Read the full writeup on github.
PT
(P.s. If anyone has the math for another blending method, please share.)
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;
Programmatically Lighten a Color
I would go for the second option. Generally speaking the RGB space is not really good for doing color manipulation (creating transition from one color to an other, lightening / darkening a color, etc). Below are two sites I've found with a quick search to convert from/to RGB to/from HSL:
- from the "Fundamentals of Computer Graphics"
- some sourcecode in C# - should be easy to adapt to other programming languages.
How to darken a given color (int)
A more Android way of doing it:
public static int manipulateColor(int color, float factor) {
int a = Color.alpha(color);
int r = Math.round(Color.red(color) * factor);
int g = Math.round(Color.green(color) * factor);
int b = Math.round(Color.blue(color) * factor);
return Color.argb(a,
Math.min(r,255),
Math.min(g,255),
Math.min(b,255));
}
You will want to use a factor less than 1.0f
to darken. try 0.8f
.
Programmatically Lighten or Darken a hex color in lua - nvim highlight colors
I wouldn't resort to bit ops in Lua 5.2 and lower, especially as Lua 5.1 lacks them (LuaJIT however does provide them); use multiplication, floor division & mod instead, and take care to clamp your values:
local function clamp(component)
return math.min(math.max(component, 0), 255)
end
function LightenDarkenColor(col, amt)
local num = tonumber(col, 16)
local r = math.floor(num / 0x10000) + amt
local g = (math.floor(num / 0x100) % 0x100) + amt
local b = (num % 0x100) + amt
return string.format("%#x", clamp(r) * 0x10000 + clamp(g) * 0x100 + clamp(b))
end
Programmatically Lighten or Darken a hex color in dart
For people who want to darken or lighten Color
instead of hex string
// ranges from 0.0 to 1.0
Color darken(Color color, [double amount = .1]) {
assert(amount >= 0 && amount <= 1);
final hsl = HSLColor.fromColor(color);
final hslDark = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0));
return hslDark.toColor();
}
Color lighten(Color color, [double amount = .1]) {
assert(amount >= 0 && amount <= 1);
final hsl = HSLColor.fromColor(color);
final hslLight = hsl.withLightness((hsl.lightness + amount).clamp(0.0, 1.0));
return hslLight.toColor();
}
// usage
final lightRed = lighten(Colors.red);
final darkBlue = darken(Colors.blue, .3);
Live Demo
Given an RGB value, how do I create a tint (or shade)?
Among several options for shading and tinting:
For shades, multiply each component by 1/4, 1/2, 3/4, etc., of its
previous value. The smaller the factor, the darker the shade.For tints, calculate (255 - previous value), multiply that by 1/4,
1/2, 3/4, etc. (the greater the factor, the lighter the tint), and add that to the previous value (assuming each.component is a 8-bit integer).
Note that color manipulations (such as tints and other shading) should be done in linear RGB. However, RGB colors specified in documents or encoded in images and video are not likely to be in linear RGB, in which case a so-called inverse transfer function needs to be applied to each of the RGB color's components. This function varies with the RGB color space. For example, in the sRGB color space (which can be assumed if the RGB color space is unknown), this function is roughly equivalent to raising each sRGB color component (ranging from 0 through 1) to a power of 2.2. (Note that "linear RGB" is not an RGB color space.)
See also Violet Giraffe's comment about "gamma correction".
Getting n shades of a given color in hex code
For what it's worth:
library(tidyverse)
colors <- c("#62B200","#FF6C91","#F57962","#00C1A9","#EE8044")
#darken each color n times in increments of steps towards black
ExpandColors <- function(colors, n, steps = 11){
if(n <= steps){
suppressWarnings({
sapply(colors, function(x){colorRampPalette(c(x, "#000000"))(steps)}) %>%
as.data.frame() %>%
filter(row_number() <= n) %>%
gather(key = original.color, value = expanded.color)
})
}else{
warning("Select n < steps!")
}
}
ExpandColors(colors, n = 2)
original.color expanded.color
1 #62B200 #62B200
2 #62B200 #58A000
3 #FF6C91 #FF6C91
4 #FF6C91 #E56182
5 #F57962 #F57962
6 #F57962 #DC6C58
7 #00C1A9 #00C1A9
8 #00C1A9 #00AD98
9 #EE8044 #EE8044
10 #EE8044 #D6733D
Related Topics
Data.Table VS Plyr Regression Output
Plot Probability Heatmap/Hexbin with Different Sized Bins
R: How to Make a Barplot with Labels Parallel (Horizontal) to Bars
How to Rotate the X-Axis Labels 90 Degrees in Levelplot
How to Fit Long Text into Ggplot2 Facet Titles
Keep Same Order as in Data Files When Using Ggplot
How to Insert Pictures into Each Individual Bar in a Ggplot Graph
Are Dataframe[ ,-1] and Dataframe[-1] the Same
Why Is the Class of a Vector the Class of the Elements of the Vector and Not Vector Itself
How to Build Multiclass Svm in R
Mclapply Returns Null Randomly
Ggplot2: Coloring Axis Text on a Faceted Plot
How to Make Variable Available to Namespace at Loading Time
Drawing Simple Mediation Diagram in R
Rolling Regression by Group in the Tidyverse