颜色渐变算法

2024-06-02

给定两种 RGB 颜色和一个矩形,我可以创建一个基本的线性渐变。这博客文章 https://bsou.io/posts/color-gradients-with-python关于如何创建它给出了很好的解释。但我想在这个算法中添加一个变量,角度。我想创建线性渐变,我可以在其中指定颜色的角度。

例如,我有一个矩形(400x100)。从颜色为红色 (255, 0, 0) 到颜色为绿色 (0, 255, 0),角度为 0°,所以我将得到以下颜色渐变。

鉴于我有相同的矩形,从颜色到颜色。但这次我将角度改为 45°。所以我应该有以下颜色渐变。


你的问题实际上由两部分组成:

  1. 如何在两种颜色之间生成平滑的颜色渐变。
  2. 如何在角度上渲染渐变。

渐变的强度在感知颜色空间中必须恒定,否则渐变中的点看起来会显得不自然的暗或亮。您可以在基于 sRGB 值的简单插值的渐变中轻松看到这一点,特别是红绿渐变在中间太暗。对线性值而不是伽玛校正值使用插值可以使红绿梯度更好,但代价是背白梯度。通过将光强度与颜色分开,您可以两全其美。

通常,当需要感知色彩空间时,实验室色彩空间 https://en.wikipedia.org/wiki/Lab_color_space将被提议。我认为有时它太过分了,因为它试图适应蓝色比同等强度的其他颜色(例如黄色)更暗的感觉。这是事实,但我们习惯于在自然环境中看到这种效果,并且在梯度中最终会出现过度补偿。

A 0.43 的幂律函数 https://en.wikipedia.org/wiki/Gamma_correction#Power_law_for_video_display研究人员通过实验确定最适合将灰光强度与感知亮度联系起来。

我在这里采集了由以下人员准备的精美样品Ian Boyd https://stackoverflow.com/a/39924008/5987并在最后添加了我自己提出的方法。我希望您会同意这种新方法在所有情况下都是优越的。

Algorithm MarkMix
   Input:
      color1: Color, (rgb)   The first color to mix
      color2: Color, (rgb)   The second color to mix
      mix:    Number, (0..1) The mix ratio. 0 ==> pure Color1, 1 ==> pure Color2
   Output:
      color:  Color, (rgb)   The mixed color
   
   //Convert each color component from 0..255 to 0..1
   r1, g1, b1 ← Normalize(color1)
   r2, g2, b2 ← Normalize(color1)

   //Apply inverse sRGB companding to convert each channel into linear light
   r1, g1, b1 ← sRGBInverseCompanding(r1, g1, b1)       
   r2, g2, b2 ← sRGBInverseCompanding(r2, g2, b2)

   //Linearly interpolate r, g, b values using mix (0..1)
   r ← LinearInterpolation(r1, r2, mix)
   g ← LinearInterpolation(g1, g2, mix)
   b ← LinearInterpolation(b1, b2, mix)

   //Compute a measure of brightness of the two colors using empirically determined gamma
   gamma ← 0.43
   brightness1 ← Pow(r1+g1+b1, gamma)
   brightness2 ← Pow(r2+g2+b2, gamma)

   //Interpolate a new brightness value, and convert back to linear light
   brightness ← LinearInterpolation(brightness1, brightness2, mix)
   intensity ← Pow(brightness, 1/gamma)

   //Apply adjustment factor to each rgb value based
   if ((r+g+b) != 0) then
      factor ← (intensity / (r+g+b))
      r ← r * factor
      g ← g * factor
      b ← b * factor
   end if

   //Apply sRGB companding to convert from linear to perceptual light
   r, g, b ← sRGBCompanding(r, g, b)

   //Convert color components from 0..1 to 0..255
   Result ← MakeColor(r, g, b)
End Algorithm MarkMix

这是 Python 中的代码:

def all_channels(func):
    def wrapper(channel, *args, **kwargs):
        try:
            return func(channel, *args, **kwargs)
        except TypeError:
            return tuple(func(c, *args, **kwargs) for c in channel)
    return wrapper

@all_channels
def to_sRGB_f(x):
    ''' Returns a sRGB value in the range [0,1]
        for linear input in [0,1].
    '''
    return 12.92*x if x <= 0.0031308 else (1.055 * (x ** (1/2.4))) - 0.055

@all_channels
def to_sRGB(x):
    ''' Returns a sRGB value in the range [0,255]
        for linear input in [0,1]
    '''
    return int(255.9999 * to_sRGB_f(x))

@all_channels
def from_sRGB(x):
    ''' Returns a linear value in the range [0,1]
        for sRGB input in [0,255].
    '''
    x /= 255.0
    if x <= 0.04045:
        y = x / 12.92
    else:
        y = ((x + 0.055) / 1.055) ** 2.4
    return y

def all_channels2(func):
    def wrapper(channel1, channel2, *args, **kwargs):
        try:
            return func(channel1, channel2, *args, **kwargs)
        except TypeError:
            return tuple(func(c1, c2, *args, **kwargs) for c1,c2 in zip(channel1, channel2))
    return wrapper

@all_channels2
def lerp(color1, color2, frac):
    return color1 * (1 - frac) + color2 * frac



def perceptual_steps(color1, color2, steps):
    gamma = .43
    color1_lin = from_sRGB(color1)
    bright1 = sum(color1_lin)**gamma
    color2_lin = from_sRGB(color2)
    bright2 = sum(color2_lin)**gamma
    for step in range(steps):
        intensity = lerp(bright1, bright2, step, steps) ** (1/gamma)
        color = lerp(color1_lin, color2_lin, step, steps)
        if sum(color) != 0:
            color = [c * intensity / sum(color) for c in color]
        color = to_sRGB(color)
        yield color

现在回答你问题的第二部分。您需要一个方程来定义表示渐变中点的线,以及与渐变端点颜色对应的线的距离。将端点放在矩形的最远角是很自然的,但从您在问题中的示例来看,这不是您所做的。我选择了 71 像素的距离来近似该示例。

生成渐变的代码需要对上面显示的代码稍作修改,以更加灵活。它不是将梯度分解为固定数量的步骤,而是根据参数进行连续统一计算t范围在 0.0 到 1.0 之间。

class Line:
    ''' Defines a line of the form ax + by + c = 0 '''
    def __init__(self, a, b, c=None):
        if c is None:
            x1,y1 = a
            x2,y2 = b
            a = y2 - y1
            b = x1 - x2
            c = x2*y1 - y2*x1
        self.a = a
        self.b = b
        self.c = c
        self.distance_multiplier = 1.0 / sqrt(a*a + b*b)

    def distance(self, x, y):
        ''' Using the equation from
            https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Line_defined_by_an_equation
            modified so that the distance can be positive or negative depending
            on which side of the line it's on.
        '''
        return (self.a * x + self.b * y + self.c) * self.distance_multiplier

class PerceptualGradient:
    GAMMA = .43
    def __init__(self, color1, color2):
        self.color1_lin = from_sRGB(color1)
        self.bright1 = sum(self.color1_lin)**self.GAMMA
        self.color2_lin = from_sRGB(color2)
        self.bright2 = sum(self.color2_lin)**self.GAMMA

    def color(self, t):
        ''' Return the gradient color for a parameter in the range [0.0, 1.0].
        '''
        intensity = lerp(self.bright1, self.bright2, t) ** (1/self.GAMMA)
        col = lerp(self.color1_lin, self.color2_lin, t)
        total = sum(col)
        if total != 0:
            col = [c * intensity / total for c in col]
        col = to_sRGB(col)
        return col

def fill_gradient(im, gradient_color, line_distance=None, max_distance=None):
    w, h = im.size
    if line_distance is None:
        def line_distance(x, y):
            return x - ((w-1) / 2.0) # vertical line through the middle
    ul = line_distance(0, 0)
    ur = line_distance(w-1, 0)
    ll = line_distance(0, h-1)
    lr = line_distance(w-1, h-1)
    if max_distance is None:
        low = min([ul, ur, ll, lr])
        high = max([ul, ur, ll, lr])
        max_distance = min(abs(low), abs(high))
    pix = im.load()
    for y in range(h):
        for x in range(w):
            dist = line_distance(x, y)
            ratio = 0.5 + 0.5 * dist / max_distance
            ratio = max(0.0, min(1.0, ratio))
            if ul > ur: ratio = 1.0 - ratio
            pix[x, y] = gradient_color(ratio)

>>> w, h = 406, 101
>>> im = Image.new('RGB', [w, h])
>>> line = Line([w/2 - h/2, 0], [w/2 + h/2, h-1])
>>> grad = PerceptualGradient([252, 13, 27], [41, 253, 46])
>>> fill_gradient(im, grad.color, line.distance, 71)

这是上面的结果:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

颜色渐变算法 的相关文章

  • Java中的对象池模式

    所以我实现了自己的对象池模式 它工作得很好并且符合预期 从列表中返回我的 老师 对象 并在没有对象时创建它们 我的问题 返回的对象 Teacher 然后需要被转换为它的专门子类之一 例如 生物老师 获得这种功能的最佳方法是什么 编辑 抱歉
  • 文件比较的逻辑

    我试图编写一个用于文件比较的程序 例如 file1 1 2 3 4 5 file2 1 2 3 4 5 如果我逐行执行 我会得到 1 1 2 2 3 4 3 5 4 5 但是 事实是这些文件之间的唯一区别是 我想要得到这样的东西 1 1 2
  • 如何在代码中设置TextView的文字颜色?

    在 XML 中 我们可以通过以下方式设置文本颜色textColor属性 比如android textColor FF0000 但如何通过编码来改变它呢 我尝试过类似的东西 holder text setTextColor R color R
  • 以最少插入次数将字符串转换为回文

    这是一个来自日常编码问题 https www dailycodingproblem com 给定一个字符串 找到可以通过插入来组成的回文数 单词中任何位置的字符数尽可能少 如果有 大于一个可以制作的最小长度的回文 返回 字典顺序最早的一个
  • 在 Android SDK 中通过单击按钮更改背景颜色不起作用

    我有一个简单的程序 可以在单击按钮后更改背景颜色 但它不起作用 public class ChangeBackgroundActivity extends Activity Called when the activity is first
  • Flutter/Dart:将十六进制颜色字符串转换为颜色?

    我们的数据库将颜色保存为String就像 AABBCC 所以我基本上正在寻找这样的函数 Color parseColor AABBCC 对于颤振 Color 类需要这样的东西Color 0xFF42A5F5 所以我需要转换 AABBCC t
  • 用于将分层平面数据(带 ParentID)转换为带缩进级别的排序平面列表的算法

    我有以下结构 MyClass guid ID guid ParentID string Name 我想创建一个数组 其中包含按层次结构中应显示的顺序排列的元素 例如 根据它们的 左 值 以及将 guid 映射到缩进级别的散列 例如 ID N
  • 按字母/字典顺序排列的两个字符串的平均值

    假设您采用字符串 a 和 z 并按字母顺序列出它们之间的所有字符串 a b c x y z 取这个列表的中点 你就会找到 m 所以这有点像取这两个字符串的平均值 您可以将其扩展到具有多个字符的字符串 例如 aa 和 zz 之间的中点将位于列
  • Tarjan 算法的非递归版本

    我有以下 Tarjan 算法的 递归 实现来查找图中的强连接组件 并且工作正常 public class StronglyConnectedComponents public static List
  • ASM 中从小端到大端的快速转换

    我在 C 中有一个 uint 类型数组 在检查程序是否在小端机器上运行后 我想将数据转换为大端类型 因为数据量可能会变得非常大 但总是均匀的 所以我想考虑将两个 uint 类型作为 ulong 类型 以获得更好的性能并在 ASM 中对其进行
  • 拓扑排序卡恩算法 BFS 或 DFS

    拓扑排序的方法是BFS还是DFS 哪个正确 我认为BFS是对的 但有些网站说DFS 有些网站说BFS 我很困惑 卡恩算法与 BFS 或 DFS 相同吗 或者BFS 或DFS 只是卡恩算法的工具 Kahn算法和DFS在实践中都用于拓扑排序 选
  • 位图中连续区域的计数是否可以比 O(r * c) 改进?

    您将获得一张由卫星拍摄的表面图像 该图像是一个位图 其中水用 标记 土地标记为 相邻组 形成一个岛屿 二 如果它们水平 垂直或对角相邻 则它们是相邻的 您的任务是打印位图中岛屿的数量 输入示例 输出 5 这是我的实现 需要O r c 空间和
  • com.jcraft.jsch.JSchException:算法协商失败

    我正在尝试从客户端计算机连接 sftp 服务器 但是 com jcraft jsch JSchException 算法协商失败 我收到这种错误 com jcraft jsch JSchException Algorithm negotiat
  • 如何编写一个简单的版本控制系统?

    我想做一个简单的版本控制系统 但我不知道如何构建我的数据和代码 这是一个简短的例子 用户登录 User has two options when uploading a file 提交新文件 提交文件的新版本 用户应该能够看到树 版本不同
  • 如何在 Perl 中生成数组的所有排列?

    生成所有内容的最佳 优雅 简单 高效 方式是什么 n perl 中数组的排列 例如 如果我有一个数组 arr 0 1 2 我想输出所有排列 0 1 2 0 2 1 1 0 2 1 2 0 2 0 1 2 1 0 它可能应该是一个返回迭代器的
  • 多线程归并排序,添加额外的线程

    我在java中的多线程合并排序算法中面临一个问题 我应该将代码修改为 3 4 5 6 7 8 线程合并排序 将原始数组划分为subArrays 目前它有2subArrays 如何将原始数组拆分为 3 4 5 6 7 8subArray是为了
  • 准备与大数据相关的设计和架构问题的最佳方法[关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 哪种算法可以解决我的婚礼餐桌问题?

    我的婚礼有 x 位客人 有 y 张桌子 有 z 个座位 客人A可以与客人B同桌 客人C不能与客人D同桌 给定所有客人之间所有连接的数据集 是否有已知的算法可以解决此类问题 我确信这种问题有一个抽象的父问题 称为 问题 x 或其他问题 或者它
  • 根据 cron 规范计算下一个计划时间

    在给定当前时间和 cron 规范的情况下 计算事件下一次运行时间的有效方法是什么 我正在寻找 每分钟循环检查是否符合规范 以外的东西 规格示例可能是 每月1日 15日15 01 每小时整点的 10 20 30 40 50 分钟 Python
  • Welzl 算法的迭代版本

    我正在使用 Welzl 算法来查找点云的最小外接圆 2d 或最小外接球体 3d 不幸的是 该算法具有非常高的递归深度 即输入点数 这个算法有迭代版本吗 我找不到任何并且不知道如何将递归更改为循环 我发现了一些迭代的最小包围圆 球算法 但它们

随机推荐