这是一个我正在工作的函数,以编程方式使十六进制颜色变亮或变暗。只需要传入一个像“3F6D2A”这样的字符串来表示颜色(col),并传入一个base10整数(amt)来表示要变亮或变暗的量。为了变暗,传入一个负数(即-20)。

我这么做的原因是,到目前为止,我找到的所有解决方案似乎都把问题复杂化了。我有一种感觉,只需几行代码就可以完成。请让我知道,如果你发现任何问题,或有任何调整,以加快它。

有趣的灯光 上校(16号); return键(col . & 0x0000FF) + amt) | ((((col > > 8) & 0x00FF) + amt) < < 8) | (16 (col > >) + amt) < < 16) . toString (16); 的 / /测试 控制台(照明颜色(“3F6D2A”,40);

对于开发人员来说,这里有一个更容易阅读的版本:

函数 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 新颜色 = g |(b << 8) |(第16<<条); 返回 newColor.toString(16); } 测试 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);

我想我的主要问题是,我说的对吗?这不是包括大多数(正常)情况吗?如果是的话,最快最小的方法是什么?我想在动画和小环境中使用,所以速度是这里的第一个最重要的因素,尺寸第二,准确性第三,可读性?嗯?不在要求清单上(对不起,我知道你们中有一半人正在泪眼汪汪!)


当前回答

下面的方法将允许您使十六进制(Hex)颜色字符串的曝光值变亮或变暗:

private static string GetHexFromRGB(byte r, byte g, byte b, double exposure)
{
    exposure = Math.Max(Math.Min(exposure, 1.0), -1.0);
    if (exposure >= 0)
    {
        return "#"
            + ((byte)(r + ((byte.MaxValue - r) * exposure))).ToString("X2")
            + ((byte)(g + ((byte.MaxValue - g) * exposure))).ToString("X2")
            + ((byte)(b + ((byte.MaxValue - b) * exposure))).ToString("X2");
    }
    else
    {
        return "#"
            + ((byte)(r + (r * exposure))).ToString("X2")
            + ((byte)(g + (g * exposure))).ToString("X2")
            + ((byte)(b + (b * exposure))).ToString("X2");
    }

}

对于GetHexFromRGB()中的最后一个参数值,传递一个介于-1和1之间的双值(-1为黑色,0不变,1为白色):

// split color (#e04006) into three strings
var r = Convert.ToByte("e0", 16);
var g = Convert.ToByte("40", 16);
var b = Convert.ToByte("06", 16);

GetHexFromRGB(r, g, b, 0.25);  // Lighten by 25%;

其他回答

如何简单的阴影颜色在PHP?

<?php
function shadeColor ($color='#cccccc', $percent=-25) {

  $color = Str_Replace("#",Null,$color);

  $r = Hexdec(Substr($color,0,2));
  $g = Hexdec(Substr($color,2,2));
  $b = Hexdec(Substr($color,4,2));

  $r = (Int)($r*(100+$percent)/100);
  $g = (Int)($g*(100+$percent)/100);
  $b = (Int)($b*(100+$percent)/100);

  $r = Trim(Dechex(($r<255)?$r:255));  
  $g = Trim(Dechex(($g<255)?$g:255));  
  $b = Trim(Dechex(($b<255)?$b:255));

  $r = ((Strlen($r)==1)?"0{$r}":$r);
  $g = ((Strlen($g)==1)?"0{$g}":$g);
  $b = ((Strlen($b)==1)?"0{$b}":$b);

  return (String)("#{$r}{$g}{$b}");
}

echo shadeColor(); // #999999

我尝试了你的函数,有一个小错误:如果最终的'r'值只有1位,结果就会出现'a0a0a',而正确的值是'0a0a0a'。 我只是通过添加这个而不是你的返回来快速修复它:

var rStr = (r.toString(16).length < 2)?'0'+r.toString(16):r.toString(16);
var gStr = (g.toString(16).length < 2)?'0'+g.toString(16):g.toString(16);
var bStr = (b.toString(16).length < 2)?'0'+b.toString(16):b.toString(16);

return (usePound?"#":"") + rStr + gStr + bStr;

也许不是很好,但确实有用。顺便说一句,功能很好。这正是我需要的。:)

这是基于函数的。我更喜欢使用步骤而不是百分比,因为这对我来说更直观。

例如,蓝色值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;

}

好吧,这个答案已经自成一体了。许多新版本,它变得愚蠢的长。非常感谢所有对这个答案做出贡献的人。但是,为了让它对大众来说简单。我把这个答案进化的所有版本/历史存档到我的github。并用最新版本在StackOverflow上重新开始。特别感谢Mike 'Pomax' Kamermans制作了这个版本。他给了我新的数学。


这个函数(pSBC)将采用HEX或RGB的网页颜色。pSBC可以使它变深或变浅,或与第二种颜色混合,也可以直接通过,但可以从十六进制转换为RGB (Hex2RGB)或RGB转换为十六进制(RGB2Hex)。你甚至不知道你使用的是什么颜色格式。

它运行得非常快,可能是最快的,特别是考虑到它的许多特性。这是一个漫长的过程。在我的github上看到整个故事。如果你想要绝对最小和最快的方法来着色或混合,请参阅下面的微函数,并使用其中一个2线速度恶魔。他们是伟大的激烈的动画,但这个版本在这里是足够快的大多数动画。

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

摆弄邮政储蓄银行 github > pSBC Wiki

特点:

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 the c1 (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 use rgb(0,0,0,1) or rgb(255,255,255,1) as your c1 (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 the c1 (to) color, if one exists. If there is no c1 (to) color, then pass 'c' in as the c1 color and it will shade and convert whatever the c0 color is. If conversion only is desired, then pass 0 in as the percentage (p) as well. If the c1 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() over rgb() when a color with an alpha channel was passed into c0 (from) and/or c1 (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 with return null; to remove this feature and make it faster and smaller. Uses Log Blending. Pass true in for l (the 4th parameter) to use Linear Blending.

代码:

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

下面的图片将有助于显示两种混合方法的区别:


微型功能

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

想了解更多信息?在github上阅读全文。

PT

(注:如果有人有其他混合方法的数学,请分享。)

我在TypeScript中把这个答案https://stackoverflow.com/a/13542669/4537906重写成一对可读的函数。

原因是在现代JavaScript中,我们不再需要关心保存字符。这是由编译器完成的。在我看来,我们的目标应该是编写可读性和可理解的代码。

这是我的方法:

tint.ts

type ColorObject = Record<"r" | "g" | "b" | "a", number>;
const singleColorSpace = 16 * 16; // 256
const blueSpace = singleColorSpace;
const greenSpace = blueSpace * singleColorSpace; // 65536
const redSpace = greenSpace * singleColorSpace; // 16777216
/* eslint-disable regex/invalid */
// adapted to TS from https://github.com/PimpTrizkit/PJs/wiki/12.-Shade,-Blend-and-Convert-a-Web-Color-(pSBC.js)
export const toColorObject = (rgbOrHex: string): ColorObject => {
    const { length } = rgbOrHex;
    const outputColor = {} as ColorObject;
    if (length > 9) {
        const rgbaColor = rgbOrHex.split(",");
        const [rgbaAndRed, green, blue, alpha] = rgbaColor;

        if (rgbaAndRed.slice(0, 3) !== "rgb") {
            throw new Error("Invalid color format");
        }
        const red = rgbaAndRed[3] === "a" ? rgbaAndRed.slice(5) : rgbaAndRed.slice(4);

        const rgbaLength = rgbaColor.length;
        if (rgbaLength < 3 || rgbaLength > 4) {
            return null;
        }
        outputColor.r = parseInt(red, 10);
        outputColor.g = parseInt(green, 10);
        outputColor.b = parseInt(blue, 10);
        outputColor.a = alpha ? parseFloat(alpha) : -1;
    } else {
        if (length === 8 || length === 6 || length < 4) {
            throw new Error("Invalid hex color format");
        }
        let HexColor = rgbOrHex;
        if (length < 6) {
            HexColor = `#${rgbOrHex[1]}${rgbOrHex[1]}${rgbOrHex[2]}${rgbOrHex[2]}${rgbOrHex[3]}${rgbOrHex[3]}${
                length > 4 ? rgbOrHex[4] + rgbOrHex[4] : ""
            }`;
        }
        if (length === 9 || length === 5) {
            const hexRed = parseInt(HexColor.slice(1, 3), 16);
            outputColor.r = hexRed;

            const hexGreen = parseInt(HexColor.slice(3, 5), 16);
            outputColor.g = hexGreen;

            const hexBlue = parseInt(HexColor.slice(5, 7), 16);
            outputColor.b = hexBlue;

            const hexAlpha = parseInt(HexColor.slice(7, 9), 16);
            outputColor.a = Math.round((hexAlpha / 255) * 100) / 100;
        } else {
            const hexRed = parseInt(HexColor.slice(1, 3), 16);
            outputColor.r = hexRed;

            const hexGreen = parseInt(HexColor.slice(3, 5), 16);
            outputColor.g = hexGreen;

            const hexBlue = parseInt(HexColor.slice(5, 7), 16);
            outputColor.b = hexBlue;

            outputColor.a = -1;
        }
    }
    return outputColor;
};

const black: ColorObject = { r: 0, g: 0, b: 0, a: -1 };
const white: ColorObject = { r: 255, g: 255, b: 255, a: -1 };
export const tint = (
    ratio: number,
    inputColor: string,
    { toColor, useLinear, reformat }: { toColor?: string; useLinear?: boolean; reformat?: boolean } = {}
) => {
    const { round } = Math;
    const clampedRatio = Math.min(Math.max(ratio, -1), 1);
    if (ratio < -1 || ratio > 1) {
        // eslint-disable-next-line no-console
        console.info(`Ratio should be between -1 and 1 and it is ${ratio}. It will be clamped to ${clampedRatio}`);
    }
    let baseColor = inputColor;
    if (inputColor[0] !== "r" && inputColor[0] !== "#") {
        baseColor = "#000";
        // eslint-disable-next-line no-console
        console.info(
            `Invalid input color format. "${inputColor}" should be rgb(a) or hex. It will fallback to "${baseColor}"`
        );
    }
    let isRGBformat = baseColor.length > 9 || baseColor.includes("rgb(");
    isRGBformat = reformat ? !isRGBformat : isRGBformat;

    if (toColor) {
        const isToColorRgbFormat = (toColor && toColor?.length > 9) || toColor?.includes("rgb(");
        isRGBformat = reformat ? !isToColorRgbFormat : isToColorRgbFormat;
    }
    const formattedBaseColor = toColorObject(baseColor);
    const isNegativeRatio = clampedRatio < 0;
    const toColorDefault = isNegativeRatio ? black : white;
    const formattedToColor = toColor && !reformat ? toColorObject(toColor) : toColorDefault;
    const toColorRatio = Math.abs(clampedRatio);
    const baseRatio = 1 - toColorRatio;

    const outputColor = {} as ColorObject;
    if (useLinear) {
        outputColor.r = round(baseRatio * formattedBaseColor.r + toColorRatio * formattedToColor.r);
        outputColor.g = round(baseRatio * formattedBaseColor.g + toColorRatio * formattedToColor.g);
        outputColor.b = round(baseRatio * formattedBaseColor.b + toColorRatio * formattedToColor.b);
    } else {
        outputColor.r = round((baseRatio * formattedBaseColor.r ** 2 + toColorRatio * formattedToColor.r ** 2) ** 0.5);
        outputColor.g = round((baseRatio * formattedBaseColor.g ** 2 + toColorRatio * formattedToColor.g ** 2) ** 0.5);
        outputColor.b = round((baseRatio * formattedBaseColor.b ** 2 + toColorRatio * formattedToColor.b ** 2) ** 0.5);
    }

    const blendedAlpha = formattedBaseColor.a * baseRatio + formattedToColor.a * toColorRatio;

    outputColor.a = formattedToColor.a < 0 ? formattedBaseColor.a : blendedAlpha;

    const hasAlpha = formattedBaseColor.a >= 0 || formattedToColor.a >= 0;
    if (isRGBformat) {
        return `rgb${hasAlpha ? "a" : ""}(${outputColor.r},${outputColor.g},${outputColor.b}${
            hasAlpha ? `,${round(outputColor.a * 1000) / 1000}` : ""
        })`;
    }
    return `#${(
        outputColor.r * redSpace +
        outputColor.g * greenSpace +
        outputColor.b * blueSpace +
        (hasAlpha ? round(outputColor.a * 255) : 0)
    )
        .toString(16)
        // If no Alpha, we remove the last 2 hex digits
        .slice(0, hasAlpha ? undefined : -2)}`;
};

还有一个笑话测试的集合

tint.test.ts

import { tint, toColorObject } from "./tint";

const rgbBlue = "rgb(20,60,200)";
const rgbaBlue = "rgba(20,60,200,0.67423)";
const hex6Cyan = "#67DAF0";
const hex3Pink = "#F3A";
const hex4Pink = "#F3A9";
const rbgBrown = "rgb(200,60,20)";
const rgbaBrown = "rgba(200,60,20,0.98631)";

describe("tint", () => {
    describe("Logarithmic blending", () => {
        describe("Shades", () => {
            it("lightens rgb color", () => {
                expect(tint(0.42, rgbBlue)).toEqual("rgb(166,171,225)");
            });
            it("darkens hex color", () => {
                expect(tint(-0.4, hex3Pink)).toEqual("#c62884");
            });
            it("lightens rgba color", () => {
                expect(tint(0.42, rgbaBrown)).toEqual("rgba(225,171,166,0.986)");
            });
            it("returns black with ratio -1", () => {
                expect(tint(-1, rgbBlue)).toEqual("rgb(0,0,0)");
            });
        });
        describe("converts color notation", () => {
            it("converts from rgba to hexa", () => {
                // expect(tint(0.42, color2, "c")).toEqual("#a6abe1ac");
                expect(tint(0.42, rgbaBlue, { reformat: true })).toEqual("#a6abe1ac");
            });
            it("converts from hexa to rgba", () => {
                // expect(tint(0, color6, "c", true)).toEqual("rgba(255,51,170,0.6)");
                expect(tint(0, hex4Pink, { reformat: true })).toEqual("rgba(255,51,170,0.6)");
            });
            it("converts and returns white with ratio 1", () => {
                expect(tint(1, hex3Pink, { reformat: true })).toEqual("rgb(255,255,255)");
            });
        });
        describe("Blends two colors", () => {
            it("blends rgba with rgba", () => {
                expect(tint(-0.5, rgbaBlue, { toColor: rgbaBrown })).toEqual("rgba(142,60,142,0.83)");
            });
            it("blends rgba with rgb", () => {
                expect(tint(0.7, rgbaBlue, { toColor: rbgBrown })).toEqual("rgba(168,60,111,0.674)");
            });
            it("blends hex with rgb", () => {
                expect(tint(0.25, hex6Cyan, { toColor: rbgBrown })).toEqual("rgb(134,191,208)");
            });
            it("blends rgb with hex", () => {
                expect(tint(0.75, rbgBrown, { toColor: hex6Cyan })).toEqual("#86bfd0");
            });
        });
    });
    describe("Linear Blending", () => {
        describe("Shades", () => {
            it("lightens rgb color", () => {
                expect(tint(0.42, rgbBlue, { useLinear: true })).toEqual("rgb(119,142,223)");
            });
            it("darkens hex color", () => {
                expect(tint(-0.4, hex3Pink, { useLinear: true })).toEqual("#991f66");
            });
            it("lightens rgba color", () => {
                expect(tint(0.42, rgbaBrown, { useLinear: true })).toEqual("rgba(223,142,119,0.986)");
            });
            it("returns black with ratio -1", () => {
                expect(tint(-1, rgbBlue, { useLinear: true })).toEqual("rgb(0,0,0)");
            });
        });
        describe("converts color notation", () => {
            it("converts from rgba to hexa", () => {
                expect(tint(0.42, rgbaBlue, { reformat: true, useLinear: true })).toEqual("#778edfac");
            });
            it("converts from hexa to rgba", () => {
                expect(tint(0, hex4Pink, { reformat: true, useLinear: true })).toEqual("rgba(255,51,170,0.6)");
            });
            it("converts and returns white with ratio 1", () => {
                expect(tint(1, hex3Pink, { useLinear: true, reformat: true })).toEqual("rgb(255,255,255)");
            });
        });
        describe("Blends two colors", () => {
            it("blends rgba with rgba", () => {
                expect(tint(-0.5, rgbaBlue, { toColor: rgbaBrown, useLinear: true })).toEqual("rgba(110,60,110,0.83)");
            });
            it("blends rgba with rgb", () => {
                expect(tint(0.7, rgbaBlue, { toColor: rbgBrown, useLinear: true })).toEqual("rgba(146,60,74,0.674)");
            });
            it("blends hex with rgb", () => {
                expect(tint(0.25, hex6Cyan, { toColor: rbgBrown, useLinear: true })).toEqual("rgb(127,179,185)");
            });
            it("blends rgb with hex", () => {
                expect(tint(0.75, rbgBrown, { toColor: hex6Cyan, useLinear: true })).toEqual("#7fb3b9");
            });
        });
    });
    describe("Error handling", () => {
        describe("When invalid hex color provided", () => {
            it.each([1, 2, 5])("throws error if hex color has %s characters", (n) => {
                const correlativeNumbers = Array.from(Array(n).keys()).join("");
                expect(() => tint(0, `#${correlativeNumbers}`)).toThrow("Invalid hex color format");
            });
        });

        describe("When ratio is not between -1 and 1", () => {
            it("clamps ratio to -1", () => {
                expect(tint(-43, rgbBlue)).toEqual("rgb(0,0,0)");
            });
            it("clamps ratio to 1", () => {
                expect(tint(42, rgbBlue)).toEqual("rgb(255,255,255)");
            });
        });
    });
});

describe("toColorObject function", () => {
    it("should return a color object from hex", () => {
        expect(toColorObject("#fff")).toEqual({
            r: 255,
            g: 255,
            b: 255,
            a: -1,
        });
    });
    it("should return a color object from hex with alpha", () => {
        expect(toColorObject("#fff6")).toEqual({
            r: 255,
            g: 255,
            b: 255,
            a: 0.4,
        });
    });
    it("should return a color object from rgb", () => {
        expect(toColorObject("rgb(255,255,255)")).toEqual({
            r: 255,
            g: 255,
            b: 255,
            a: -1,
        });
    });
    it("should return a color object from rgba", () => {
        expect(toColorObject("rgba(255,255,255,1)")).toEqual({
            r: 255,
            g: 255,
            b: 255,
            a: 1,
        });
    });
    describe("Error handling", () => {
        it("should throw error if invalid color provided", () => {
            expect(() => toColorObject("foo")).toThrow("Invalid hex color format");
        });
        it("should throw error if invalid color provided", () => {
            expect(() => toColorObject("invalid color")).toThrow("Invalid color format");
        });
    });
});

希望你喜欢。这很简单,但效果很好