以编程方式淡化颜色

Posted

技术标签:

【中文标题】以编程方式淡化颜色【英文标题】:Programmatically Lighten a Color 【发布时间】:2010-09-13 14:24:28 【问题描述】:

动机

我想找到一种方法来获取任意颜色并使其变亮一些阴影,这样我就可以通过编程创建一个从一种颜色到较浅版本的漂亮渐变。渐变将用作 UI 中的背景。

可能性1

显然,我可以将 RGB 值分开并单独增加一定数量。这真的是我想要的吗?

可能性2

我的第二个想法是将 RGB 转换为 HSV/HSB/HSL(色相、饱和度、值/亮度/亮度),稍微提高亮度,降低饱和度,然后再转换回 RGB。这一般会产生预期的效果吗?

【问题讨论】:

如果背景是白色的,那么修改 alpha/透明度通常可以很好地作为“淡化”颜色的技巧。 【参考方案1】:

作为Wedge said,您想要乘以使事物更亮,但这仅在其中一种颜色饱和(即达到 255 或更大)之前有效。那时,您可以将值限制为 255,但随着颜色变浅,您会巧妙地改变色调。要保持色调,您需要保持 (middle-lowest)/(highest-lowest) 的比率。

这里有两个 Python 函数。第一个实现了简单的方法,如果它们超过,它只会将 RGB 值钳制为 255。第二个重新分配多余的值以保持色调不变。

def clamp_rgb(r, g, b):
    return min(255, int(r)), min(255, int(g)), min(255, int(b))

def redistribute_rgb(r, g, b):
    threshold = 255.999
    m = max(r, g, b)
    if m <= threshold:
        return int(r), int(g), int(b)
    total = r + g + b
    if total >= 3 * threshold:
        return int(threshold), int(threshold), int(threshold)
    x = (3 * threshold - total) / (3 * m - total)
    gray = threshold - x * m
    return int(gray + x * r), int(gray + x * g), int(gray + x * b)

我创建了一个渐变,从 RGB 值 (224,128,0) 开始,然后将其乘以 1.0、1.1、1.2 等,直到 2.0。上半部分是使用clamp_rgb 的结果,下半部分是使用redistribute_rgb 的结果。我认为很容易看出重新分配溢出会产生更好的结果,而不必离开 RGB 颜色空间。

为了比较,这里是 HLS 和 HSV 颜色空间中的相同渐变,由 Python 的 colorsys 模块实现。仅修改了L 组件,并对生成的 RGB 值执行了钳位。结果相似,但需要对每个像素进行色彩空间转换。

【讨论】:

在对此表示赞同后,我意识到这太过分了。您需要做的就是以一定的不透明度将 alpha 与白色混合(即将颜色分量插入相同的分数,朝向 255)。 @Andy 看一下示例。左边的前两个盒子里没有任何白色,它们是完全饱和的。如果我从较深的颜色开始会更多。 啊,我明白了,所以您的代码与 HLS 颜色空间具有相同的线性不连续性。我可以看到这将如何产生更丰富的渐变。因为我只是想为悬停高亮调亮颜色而不是制作渐变,所以我期待一个更简单的方法 @Andy 还有另一个答案建议使用白色进行线性插值,我相信这正是您所追求的。 是的,我打算自己添加这个答案,直到我看到其他人已经完成了。干杯!【参考方案2】:

我会选择第二个选项。一般来说,RGB 空间并不适合进行颜色处理(创建从一种颜色到另一种颜色的过渡,使颜色变亮/变暗等)。以下是我通过快速搜索找到的两个网站,可将 RGB 转换为 HSL 或从 HSL 转换为 RGB:

from the "Fundamentals of Computer Graphics" some sourcecode in C# - 应该很容易适应其他编程语言。

【讨论】:

转换源码的好替换链接:bobpowell.net/RGBHSB.aspx 另一个参考链接:social.msdn.microsoft.com/Forums/vstudio/en-US/… 2021 年更新:Curtis Yallop 的第一个链接不再有效,第二个仅指更多链接。所以这是一步一步的编程收据:css-tricks.com/converting-color-spaces-in-javascript 以下是色彩空间转换背后的精彩见解:***.com/a/39147465/818827 - 在摆弄方便、简单的方法之后,我最终选择了 OP 的第二个选项有其不受欢迎的限制。【参考方案3】:

在 C# 中:

public static Color Lighten(Color inColor, double inAmount)

  return Color.FromArgb(
    inColor.A,
    (int) Math.Min(255, inColor.R + 255 * inAmount),
    (int) Math.Min(255, inColor.G + 255 * inAmount),
    (int) Math.Min(255, inColor.B + 255 * inAmount) );

我到处都用过这个。

【讨论】:

非常方便,简单。现在在 VB.NET 应用程序中使用它。 见***.com/questions/801406/… 如果您使用的是using System.Windows.Media.Color;,您不应该转换为字节吗?【参考方案4】:

System.Windows.Forms 命名空间中的 ControlPaint 类具有静态方法 Light 和 Dark:

public static Color Dark(Color baseColor, float percOfDarkDark);

这些方法使用 HLSColor 的私有实现。我希望这个结构是公开的并且在 System.Drawing 中。

或者,您可以在 Color 结构上使用 GetHue、GetSaturation、GetBrightness 来获取 HSB 组件。不幸的是,我没有找到反向转换。

【讨论】:

【参考方案5】:

将其转换为 RGB 并在原始颜色和目标颜色(通常为白色)之间进行线性插值。所以,如果你想要两种颜色之间的 16 种色调,你可以这样做:


for(i = 0; i < 16; i++)

  colors[i].R = start.R + (i * (end.R - start.R)) / 15;
  colors[i].G = start.G + (i * (end.G - start.G)) / 15;
  colors[i].B = start.B + (i * (end.B - start.B)) / 15;

【讨论】:

在大多数情况下,只需要开始和结束颜色,因为选择的图形库可能已经内置了渐变功能。 并且通过使用整数除法,您消除了对舍入(下限)的需要。聪明。【参考方案6】:

为了获得给定颜色的更亮或更暗版本,您应该修改其亮度。即使不将颜色转换为 HSL 或 HSB 颜色,您也可以轻松完成此操作。例如,要使颜色变浅,您可以使用以下代码:

float correctionFactor = 0.5f;
float red = (255 - color.R) * correctionFactor + color.R;
float green = (255 - color.G) * correctionFactor + color.G;
float blue = (255 - color.B) * correctionFactor + color.B;
Color lighterColor = Color.FromArgb(color.A, (int)red, (int)green, (int)blue);

如果您需要更多详细信息,请read the full story on my blog。

【讨论】:

+1,尼克斯方法,正确的方法。但是您仍然可以像 Adam Rosenfield 那样跳过浮点数的转换,将校正因子拆分为步长和索引,这也给出了两个可理解的值,而不是一个介于 0.0 和 1.0 之间的抽象数字。例如您的校正因子是 Rosenfield 的 i/15,同时假设白色作为目标颜色。【参考方案7】:

转换为 HS(LVB)、增加亮度然后再转换回 RGB 是可靠地使颜色变亮而不影响色调和饱和度值的唯一方法(即仅使颜色变亮而不以任何其他方式改变它) .

【讨论】:

【参考方案8】:

之前有人问过一个非常相似的问题,答案很有用: How do I determine darker or lighter color variant of a given color?

简答:如果您只需要“足够好”,请将 RGB 值乘以一个常数,如果您需要准确度,请转换为 HSV。

【讨论】:

赞成引用类似问题,但我不同意 RGB 的建议 - 使用 HSV。【参考方案9】:

我使用 Andrew 的答案和 Mark 的答案制作了 this(截至 1/2013 没有 ff 的范围输入)。

function calcLightness(l, r, g, b) 
    var tmp_r = r;
    var tmp_g = g;
    var tmp_b = b;

    tmp_r = (255 - r) * l + r;
    tmp_g = (255 - g) * l + g;
    tmp_b = (255 - b) * l + b;

    if (tmp_r > 255 || tmp_g > 255 || tmp_b > 255) 
        return  r: r, g: g, b: b ;
    else 
        return  r:parseInt(tmp_r), g:parseInt(tmp_g), b:parseInt(tmp_b) 

【讨论】:

【参考方案10】:

我已经做到了这两种方式 - 使用可能性 2 可以获得更好的结果。

您为可能性 1 构建的任何简单算法都可能仅适用于有限范围的起始饱和度。

如果 (1) 您可以限制使用的颜色和亮度,并且 (2) 您在渲染中执行了大量计算,则您可能需要查看 Poss 1。

为 UI 生成背景不需要太多的着色计算,所以我建议 Poss 2。

-阿尔。

【讨论】:

【参考方案11】:

如果您想产生渐变淡出效果,我建议进行以下优化:您应该只计算目标颜色,而不是为每种颜色执行 RGB->HSB->RGB。一旦知道了目标 RGB,就可以简单地计算 RGB 空间中的中间值,而无需来回转换。是否计算使用某种曲线的线性过渡取决于您。

【讨论】:

【参考方案12】:

方法一:RGB转HSL,调整HSL,转回RGB。

方法 2:Lerp RGB 颜色值 - http://en.wikipedia.org/wiki/Lerp_(computing)

有关方法 2 的 C# 实现,请参阅 my answer to this similar question。

【讨论】:

【参考方案13】:

假设您将 alpha 混合为白色:

oneMinus = 1.0 - amount
r = amount + oneMinus * r
g = amount + oneMinus * g
b = amount + oneMinus * b

其中amount 是从 0 到 1,0 返回原始颜色,1 返回白色。

如果您要点亮以显示禁用的内容,您可能希望与任何背景颜色混合:

oneMinus = 1.0 - amount
r = amount * dest_r + oneMinus * r
g = amount * dest_g + oneMinus * g
b = amount * dest_b + oneMinus * b

其中(dest_r, dest_g, dest_b) 是要混合的颜色,amount 是从 0 到 1,零返回 (r, g, b),1 返回 (dest.r, dest.g, dest.b)

【讨论】:

我认为这应该是最佳答案。同样,要使您变暗,只需将 alpha 混合为黑色(即,将分量乘以一些 amount &lt; 1)。【参考方案14】:

直到它成为与我原来的问题相关的问题之后,我才发现这个问题。

但是,利用这些出色答案的洞察力。我为此拼凑了一个不错的两线函数:

Programmatically Lighten or Darken a hex color (or rgb, and blend colors)

它是方法 1 的一个版本。但考虑到了过度饱和。就像基思在上面的回答中所说的那样;使用 Lerp 似乎解决了 Mark 提到的相同问题,但没有重新分配。 shadeColor2 的结果应该更接近于使用 HSL 的正确方式,但没有开销。

【讨论】:

【参考方案15】:

派对有点晚了,但如果你使用javascriptnodejs,你可以使用tinycolor library,并按照你想要的方式操纵颜色:

tinycolor("red").lighten().desaturate().toHexString() // "#f53d3d" 

【讨论】:

【参考方案16】:

我会先尝试 #1,但 #2 听起来不错。尝试自己做,看看您对结果是否满意,听起来您可能需要 10 分钟才能完成测试。

【讨论】:

【参考方案17】:

从技术上讲,我认为两者都不正确,但我相信您想要选项 #2 的变体。问题在于采用 RGB 990000 并“点亮”它实际上只会添加到红色通道(值、亮度、亮度),直到你到达 FF。之后(纯红色),它会降低 饱和度 一直到纯白色。

转换变得烦人,特别是因为您不能直接往返于 RGB 和 Lab,但我认为您确实想要分离色度和亮度值,只需修改亮度以真正实现您想要的。

【讨论】:

【参考方案18】:

这是一个在 Python 中使 RGB 颜色变亮的示例:

def lighten(hex, amount):
    """ Lighten an RGB color by an amount (between 0 and 1),

    e.g. lighten('#4290e5', .5) = #C1FFFF
    """
    hex = hex.replace('#','')
    red = min(255, int(hex[0:2], 16) + 255 * amount)
    green = min(255, int(hex[2:4], 16) + 255 * amount)
    blue = min(255, int(hex[4:6], 16) + 255 * amount)
    return "#%X%X%X" % (int(red), int(green), int(blue))

【讨论】:

我完全不知道这是做什么的。【参考方案19】:

这是基于 Mark Ransom 的 answer。

clampRGB 函数试图保持色调,但是它错误地计算了缩放以保持相同的亮度。这是因为计算直接使用了非线性的 sRGB 值。

这是一个与 clampRGB 相同的 Java 版本(尽管其值介于 0 到 1 之间),也能保持亮度:

private static Color convertToDesiredLuminance(Color input, double desiredLuminance) 
  if(desiredLuminance > 1.0) 
    return Color.WHITE;
  
  if(desiredLuminance < 0.0) 
    return Color.BLACK;
  

  double ratio = desiredLuminance / luminance(input);
  double r = Double.isInfinite(ratio) ? desiredLuminance : toLinear(input.getRed()) * ratio;
  double g = Double.isInfinite(ratio) ? desiredLuminance : toLinear(input.getGreen()) * ratio;
  double b = Double.isInfinite(ratio) ? desiredLuminance : toLinear(input.getBlue()) * ratio;

  if(r > 1.0 || g > 1.0 || b > 1.0)   // anything outside range?
    double br = Math.min(r, 1.0);  // base values
    double bg = Math.min(g, 1.0);
    double bb = Math.min(b, 1.0);
    double rr = 1.0 - br;  // ratios between RGB components to maintain
    double rg = 1.0 - bg;
    double rb = 1.0 - bb;

    double x = (desiredLuminance - luminance(br, bg, bb)) / luminance(rr, rg, rb);

    r = 0.0001 * Math.round(10000.0 * (br + rr * x));
    g = 0.0001 * Math.round(10000.0 * (bg + rg * x));
    b = 0.0001 * Math.round(10000.0 * (bb + rb * x));
  

  return Color.color(toGamma(r), toGamma(g), toGamma(b));

及配套功能:

private static double toLinear(double v)   // inverse is #toGamma
  return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);


private static double toGamma(double v)   // inverse is #toLinear
  return v <= 0.0031308 ? v * 12.92 : 1.055 * Math.pow(v, 1.0 / 2.4) - 0.055;


private static double luminance(Color c) 
  return luminance(toLinear(c.getRed()), toLinear(c.getGreen()), toLinear(c.getBlue()));


private static double luminance(double r, double g, double b) 
  return r * 0.2126 + g * 0.7152 + b * 0.0722;

【讨论】:

以上是关于以编程方式淡化颜色的主要内容,如果未能解决你的问题,请参考以下文章

Swift iOS Sprite Kit - 将精灵从一种颜色淡化为另一种颜色[关闭]

以编程方式更改 UINavigationbar 背景颜色和标题字体/颜色

淡化 UILabel 的背景颜色? (静态展示)[重复]

如何淡化 UIView 背景颜色 - iOS 5

如何淡化背景而不是仅仅改变颜色

以编程方式更改按钮颜色