以编程方式使十六进制颜色(或 rgb 和混合颜色)变亮或变暗

IT技术 javascript colors hex
2021-02-03 16:32:02

这是我正在开发的一个函数,用于通过特定数量以编程方式使十六进制颜色变亮或变暗。只需传入一个类似"3F6D2A"颜色的字符串( col) 和一个 base10 整数 ( amt) 来表示变亮或变暗的数量。要变暗,请传入一个负数(即-20)。

我这样做的原因是因为我找到的所有解决方案,到目前为止,它们似乎使问题过于复杂。我有一种感觉,只需几行代码就可以完成。如果您发现任何问题,或者有任何调整可以加快速度,请告诉我。

function LightenDarkenColor(col, amt) {
  col = parseInt(col, 16);
  return (((col & 0x0000FF) + amt) | ((((col >> 8) & 0x00FF) + amt) << 8) | (((col >> 16) + amt) << 16)).toString(16);
}


// TEST
console.log( LightenDarkenColor("3F6D2A",40) );

对于开发使用,这里是一个更容易阅读的版本:

function LightenDarkenColor(col, amt) {
  var num = parseInt(col, 16);
  var r = (num >> 16) + amt;
  var b = ((num >> 8) & 0x00FF) + amt;
  var g = (num & 0x0000FF) + amt;
  var newColor = g | (b << 8) | (r << 16);
  return newColor.toString(16);
}


// TEST
console.log(LightenDarkenColor("3F6D2A", -40));

最后一个版本可以处理开头可能(也可能没有)带有“#”的颜色。加上调整不正确的颜色值:

function LightenDarkenColor(col,amt) {
    var usePound = false;
    if ( col[0] == "#" ) {
        col = col.slice(1);
        usePound = true;
    }

    var num = parseInt(col,16);

    var r = (num >> 16) + amt;

    if ( r > 255 ) r = 255;
    else if  (r < 0) r = 0;

    var b = ((num >> 8) & 0x00FF) + amt;

    if ( b > 255 ) b = 255;
    else if  (b < 0) b = 0;

    var g = (num & 0x0000FF) + amt;

    if ( g > 255 ) g = 255;
    else if  ( g < 0 ) g = 0;

    return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16);
}

好的,所以现在它不仅仅是几行,但它看起来要简单得多,如果您不使用“#”并且不需要检查超出范围的颜色,那么它只是几行。

如果不使用“#”,您可以将其添加到代码中,例如:

var myColor = "3F6D2A";
myColor = LightenDarkenColor(myColor,10);
thePlaceTheColorIsUsed = ("#" + myColor);

我想我的主要问题是,我在这里正确吗?这不包括某些(正常)情况吗?

6个回答

好吧,这个答案已经成为它自己的野兽。许多新版本,它变得愚蠢了很久。非常感谢这个答案的所有贡献者。但是,为了让大众保持简单。我将这个答案演变的所有版本/历史存档到我的github并使用最新版本在 StackOverflow 上重新开始。特别感谢Mike 'Pomax' Kamermans 制作了这个版本。他给了我新的数学。


此函数 ( pSBC) 将采用 HEX 或 RGB 网络颜色。pSBC可以将其着色为更暗或更亮,或将其与第二种颜色混合,也可以将其直接传递但从十六进制转换为 RGB (Hex2RGB) 或从 RGB 转换为十六进制 (RGB2Hex)。您甚至都不知道您使用的是什么颜色格式。

这运行得非常快,可能是最快的,尤其是考虑到它的许多功能。酝酿已久。在我的github上查看整个故事如果您想要以绝对最小和最快的方式进行着色或混合,请参阅下面的微功能并使用 2 线速度恶魔之一。它们非常适合激烈的动画,但这里的这个版本对于大多数动画来说已经足够快了。

此功能使用对数混合或线性混合。但是,它不会转换为 HSL 以适当地使颜色变亮或变暗。因此,此函数的结果将不同于使用 HSL 的那些更大、更慢的函数。

jsFiddle 与 pSBC

github > pSBC 维基

特征:

  • 自动检测并接受字符串形式的标准十六进制颜色。例如:"#AA6622""#bb551144"
  • 自动检测并接受字符串形式的标准 RGB 颜色。例如:"rgb(123,45,76)""rgba(45,15,74,0.45)"
  • 按百分比将颜色着色为白色或黑色。
  • 按百分比混合颜色。
  • 进行 Hex2RGB 和 RGB2Hex 转换同时或单独进行。
  • 接受 3 位(或 4 位带 alpha)十六进制颜色代码,格式为 #RGB(或 #RGBA)。它会扩展它们。例如:"#C41"变成"#CC4411".
  • 接受并(线性)混合 Alpha 通道。如果c0(from) 颜色或c1(to) 颜色具有 alpha 通道,则返回的颜色将具有 alpha 通道。如果两种颜色都有一个 alpha 通道,则返回的颜色将是使用给定百分比的两个 alpha 通道的线性混合(就像它是一个普通的颜色通道)。如果两种颜色中只有一种具有 alpha 通道,则此 alpha 将直接传递给返回的颜色。这允许在保持透明度级别的同时混合/着色透明颜色。或者,如果透明度级别也应该混合,请确保两种颜色都有 alphas。着色时,它将直接通过 alpha 通道。如果您想要也为 alpha 通道着色的基本着色,则使用rgb(0,0,0,1)rgb(255,255,255,1)作为您的c1(到)颜色(或它们的十六进制等价物)。对于 RGB 颜色,返回颜色的 alpha 通道将四舍五入到小数点后 3 位。
  • 使用混合时,RGB2Hex 和 Hex2RGB 转换是隐式的。不管c0(从)颜色;返回的颜色将始终采用c1(到)颜色的颜色格式(如果存在)。如果没有c1(to) 颜色,则'c'作为c1颜色传入,它将着色并转换任何c0颜色。如果只需要转换,那么也0作为百分比 ( p)传入如果c1省略颜色或string传入,则不会转换。
  • 辅助功能也被添加到全局中。pSBCr可以传递十六进制或 RGB 颜色,并返回一个包含此颜色信息的对象。其形式为:{r: XXX, g: XXX, b: XXX, a: X.XXX}。其中.r.g和 的.b范围为 0 到 255。当没有 alpha 时:.a为 -1。否则:.a范围为 0.000 到 1.000。
  • 对于 RGB 输出,它会rgba()rgb()带有 alpha 通道的颜色传入c0(from)和/或c1(to)时输出。
  • 已添加小错误检查。这并不完美。它仍然可能崩溃或造成混乱。但它会抓住一些东西。基本上,如果结构在某些方面有问题,或者百分比不是数字或超出范围,它将返回null. 一个例子:pSBC(0.5,"salt") == null,它认为#salt是一个有效的颜色。删除以 结尾的四行return null;以删除此功能并使其更快更小。
  • 使用日志混合。传入truefor l(第四个参数)以使用线性混合。

代码:

// 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)
}

用法:

// 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}

下图将有助于显示两种混合方法的差异:


微功能

如果你真的想要速度和大小,你将不得不使用 RGB 而不是 HEX。RGB 更直接和简单,HEX 写得太慢,而且对于简单的两行代码(即,它可能是 3、4、6 或 8 位十六进制代码)有太多的风格。您还需要牺牲一些功能,没有错误检查,没有 HEX2RGB 或 RGB2HEX。同样,您还需要为颜色混合数学选择一个特定的函数(根据下面的函数名称),以及是否需要着色或混合。这些函数确实支持 alpha 通道。当两种输入颜色都有 alpha 时,它将线性混合它们。如果两种颜色中只有一种具有 alpha,它将直接传递给生成的颜色。下面是两个非常快速和小巧的线性函数:

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:")");
}

想了解更多信息?阅读github上的完整文章

PT

(Ps 如果有人有另一种混合方法的数学方法,请分享。)

抱歉,我显然错过了这一点。可能有两个原因。第一个也是显而易见的是,我使用 Math.Round 并且你不能使用十进制数字来准确着色(颜色没有十六进制小数)。例如,如果红色通道是 8,则将10%添加8.89然后9.09%带走9,你得到8.18198是一个不好的例子。但它仍然说明您正在9.09%使用9而不是8.8所以那里可能有一些数字与我这里的例子不完全相同。
2021-03-13 16:32:02
这是更新的 shadeColor2 版本的 PHP 版本: function shadeColor2($color, $percent) { $color = str_replace("#", "", $color); $t=$percent<0?0:255; $p=$percent<0?$percent*-1:$percent; $RGB = str_split($color, 2); $R=hexdec($RGB[0]); $G=hexdec($RGB[1]); $B=hexdec($RGB[2]); return '#'.substr(dechex(0x1000000+(round(($t-$R)*$p)+$R)*0x10000+(round(($t-$G)*$p)+$G)*0x100+(round(($t-$B)*$p)+$B)),1); }
2021-03-14 16:32:02
很棒的帖子... :) ... 刚刚创建了它的 Swift 扩展:gist.github.com/matejukmar/1da47f7a950d1ba68a95
2021-03-17 16:32:02
2021-03-20 16:32:02
我用TinyColor -tinycolor.darken(color,amount);
2021-04-10 16:32:02

我做了一个对我来说非常有用的解决方案:

function shadeColor(color, percent) {

    var R = parseInt(color.substring(1,3),16);
    var G = parseInt(color.substring(3,5),16);
    var B = parseInt(color.substring(5,7),16);

    R = parseInt(R * (100 + percent) / 100);
    G = parseInt(G * (100 + percent) / 100);
    B = parseInt(B * (100 + percent) / 100);

    R = (R<255)?R:255;  
    G = (G<255)?G:255;  
    B = (B<255)?B:255;  

    var RR = ((R.toString(16).length==1)?"0"+R.toString(16):R.toString(16));
    var GG = ((G.toString(16).length==1)?"0"+G.toString(16):G.toString(16));
    var BB = ((B.toString(16).length==1)?"0"+B.toString(16):B.toString(16));

    return "#"+RR+GG+BB;
}

示例减轻:

shadeColor("#63C6FF",40);

变暗示例:

shadeColor("#63C6FF",-40);
为了让它与黑色一起工作,我刚刚做了这个黑客 var R = parseInt(color.substring(1, 3), 16) var G = parseInt(color.substring(3, 5), 16) var B = parseInt(color.substring(5, 7), 16) if (R == 0) R = 32; if (G == 0) G = 32; if (B == 0) B = 32;
2021-03-13 16:32:02
不错,我喜欢百分比!+1芹苴,我也可能会做R = ((R<255)?R:255).toString(16);那么R = R.length==1 ? "0"+R : R速度。我不确定 toUpperCase 的意义?
2021-03-14 16:32:02
不适用于#ff0000、#00ff00、#0000ff 等纯色
2021-03-14 16:32:02
这是不必要的。我只在测试时添加漂亮的打印。我会编辑那个。
2021-03-15 16:32:02
非常好。但是,无论是什么颜色,100% 浅的颜色都应该不会变成全白,而 100% 的深色总是黑色吗?似乎 -100 确实会使任何颜色变黑,但 100(正值)不会使其完全变白。
2021-04-04 16:32:02

这是一个基于 Eric 的回答的超级简单的单线

function adjust(color, amount) {
    return '#' + color.replace(/^#/, '').replace(/../g, color => ('0'+Math.min(255, Math.max(0, parseInt(color, 16) + amount)).toString(16)).substr(-2));
}

例子:

adjust('#ffffff', -20) => "#ebebeb"
adjust('000000', 20) => "#141414"
您可以通过在该正则表达式中使用\w\w(不匹配#)而不是删除并重新添加#. 我也不太明白为什么要添加0然后删除它?这是我没有关注的一些 JS 东西(你已经在使用toString(),所以你不需要投射它?)无论如何,我最终得到了以下内容,它们的行为似乎相同:var adj = (c, a) => c.replace(/\w\w/g, m => Math.min(255, Math.max(0, parseInt(m, 16) + a)).toString(16))
2021-03-23 16:32:02
“超级简单”。
2021-03-26 16:32:02
为此添加了一个演示器... jsfiddle.net/Abeeee/xeos0p42/20
2021-03-26 16:32:02
@MartinTournoij :'0'+substr(-2)用于 0 到 15 之间的那些数字,这些数字在十六进制中只有一位,因此会给出格式错误的十六进制颜色字符串。他在前面加上'0'并保留最后两位数字,这样9就变成了'09',而不仅仅是'9'。不过第一点很好!👍
2021-04-02 16:32:02
金额是多少?可以帮助澄清它是否是 px、百分比等。
2021-04-06 16:32:02

我在这里加了 2 美分,这是不同答案的令人满意的小组合:

const colorShade = (col, amt) => {
  col = col.replace(/^#/, '')
  if (col.length === 3) col = col[0] + col[0] + col[1] + col[1] + col[2] + col[2]

  let [r, g, b] = col.match(/.{2}/g);
  ([r, g, b] = [parseInt(r, 16) + amt, parseInt(g, 16) + amt, parseInt(b, 16) + amt])

  r = Math.max(Math.min(255, r), 0).toString(16)
  g = Math.max(Math.min(255, g), 0).toString(16)
  b = Math.max(Math.min(255, b), 0).toString(16)

  const rr = (r.length < 2 ? '0' : '') + r
  const gg = (g.length < 2 ? '0' : '') + g
  const bb = (b.length < 2 ? '0' : '') + b

  return `#${rr}${gg}${bb}`
}

接受以#或不以 6 个字符或 3 个字符开头的颜色

使用示例: colorShade('#54b946', -40)

这是 4 种颜色的输出,每种颜色有 3 种颜色较浅,3 种颜色较深(此处数量为 40 的倍数)。

在此处输入图片说明

这是我根据您的功能使用的。我更喜欢使用步数而不是百分比,因为它对我来说更直观。

例如,200 蓝值的 20% 与 40 蓝值的 20% 大不相同。

无论如何,这是我的修改,感谢您的原始功能。

function adjustBrightness(col, amt) {

    var usePound = false;

    if (col[0] == "#") {
        col = col.slice(1);
        usePound = true;
    }

    var R = parseInt(col.substring(0,2),16);
    var G = parseInt(col.substring(2,4),16);
    var B = parseInt(col.substring(4,6),16);

    // to make the colour less bright than the input
    // change the following three "+" symbols to "-"
    R = R + amt;
    G = G + amt;
    B = B + amt;

    if (R > 255) R = 255;
    else if (R < 0) R = 0;

    if (G > 255) G = 255;
    else if (G < 0) G = 0;

    if (B > 255) B = 255;
    else if (B < 0) B = 0;

    var RR = ((R.toString(16).length==1)?"0"+R.toString(16):R.toString(16));
    var GG = ((G.toString(16).length==1)?"0"+G.toString(16):G.toString(16));
    var BB = ((B.toString(16).length==1)?"0"+B.toString(16):B.toString(16));

    return (usePound?"#":"") + RR + GG + BB;

}
发现这比最佳答案有用得多,因为最佳答案使我的颜色非常强烈,而不仅仅是更暗。干杯埃里克
2021-03-14 16:32:02