【问题标题】:Algorithm to convert any positive integer to an RGB value将任何正整数转换为 RGB 值的算法
【发布时间】:2010-03-03 21:06:20
【问题描述】:

我们有一个要显示的热图。构成要显示的值的数字是未知的(除了它们将是正整数)。数字的范围也是未知的(同样,除了它们将是正整数)。范围可以在 0 到 200 或 578 到 1M 或其他任何值之间。这取决于未知的数据。

我们希望获取未知范围的正整数并将其转换为缩放(压缩)范围,以便在热图中以 RGB 值显示。我希望这是有道理的。 谢谢!

我想澄清一下,最小/最大值需要“插入”到论坛中。

【问题讨论】:

    标签: algorithm rgb


    【解决方案1】:

    您需要首先找到这些值的范围以获得最小值和最大值。 然后,您需要创建一个色标,如此图像下方的条形图。您可以尝试使用不同的函数将整数映射到 RGB。您需要 3 个函数 R(X)、G(X)、B(X)。看下面的图像,它看起来像中间的 B(X) 峰,末端的 R(X) 峰,绿色在其他地方。只要您确保您永远不会为某个 X 值获得两个 (RGB),那么您就完成了转换。


    (来源:globalwarmingart.com)

    编辑: 想想看,你可以在 YUV 空间周围采样一些单位圆。 alt text http://www.biocrawler.com/w/images/e/ec/Yuv.png

    或者甚至只是下载一个高分辨率彩条并对其进行采样。

    编辑 2: 我刚刚面对彩条生成并记住了 MATLAB/Octave 彩条代码。我绘制了他们的数据并得到了以下图像。

    【讨论】:

    • 我要补充一点,在 HSV 空间中最容易创建颜色条
    • 我喜欢这个主意。 . .但是由于在我们提取数据以填充热图之前我们不会知道最小值/最大值,所以我们如何动态创建颜色条值?最小值/最大值必须是算法/公式的一部分。 . .
    • @Richard:在这种情况下,每次输入超出原始范围的新数据时,您都必须重新绘制彩色显示。 R(X), G(X), B(X) 函数可以采用标准化的 X 值,当更高的值进入时,您只需要更新标准化函数。这会导致所有颜色看起来变得更冷作为更高的值进来了。如果不提前知道范围,我没有办法解决这个问题。
    • 是的,我们每次都会重绘彩色显示。我的问题是,如何标准化这些颜色?以及如何以对数或指数方式进行计算,即如果值的范围很大(例如在 200 和 10,000,000 之间),我们如何仍然表示 200。特别是如果大多数值都接近 200。不知何故,它必须被加权到大多数值所在的频谱末端。 . .
    • 另一方面,如果它是相当均匀的,例如200、500k、1M,那么分布也应该是均匀的。 ..
    【解决方案2】:

    您想将数据值转换为光的频率:

    • 较低的波长 = 较冷的颜色 = 蓝色
    • 更高的波长 = 更暖的颜色 = 更红

    可见光的频率从大约 350nm(紫色)到 650nm(红色):


    (来源:gamonline.com)

    以下函数将你指定范围内的数字转换为可见光范围,然后获取rgb:

    function DataPointToColor(Value, MinValue, MaxValue: Real): TColor;
    var
       r, g, b: Byte;
       WaveLength: Real;
    begin
       WaveLength := GetWaveLengthFromDataPoint(Value, MinValue, MaxValue);
       WavelengthToRGB(Wavelength, r, g, b);
       Result := RGB(r, g, b);
    end;
    

    使用我脑海中写下的功能:

    function GetWaveLengthFromDataPoint(Value: Real; MinValues, MaxValues: Real): Real;
    const
       MinVisibleWaveLength = 350.0;
       MaxVisibleWaveLength = 650.0;
    begin
       //Convert data value in the range of MinValues..MaxValues to the 
       //range 350..650
    
       Result := (Value - MinValue) / (MaxValues-MinValues) *
             (MaxVisibleWavelength - MinVisibleWavelength) +
             MinVisibleWaveLength;
    end;
    

    还有一个function i found on the internets,将波长转换为RGB:

    PROCEDURE WavelengthToRGB(CONST Wavelength:  Nanometers;
                              VAR R,G,B:  BYTE);
      CONST
        Gamma        =   0.80;
        IntensityMax = 255;
      VAR
        Blue   :  DOUBLE;
        factor :  DOUBLE;
        Green  :  DOUBLE;
        Red    :  DOUBLE;
      FUNCTION Adjust(CONST Color, Factor:  DOUBLE):  INTEGER;
      BEGIN
        IF   Color = 0.0
        THEN RESULT := 0     // Don't want 0^x = 1 for x <> 0
        ELSE RESULT := ROUND(IntensityMax * Power(Color * Factor, Gamma))
      END {Adjust};
    BEGIN
      CASE TRUNC(Wavelength) OF
        380..439:
          BEGIN
            Red   := -(Wavelength - 440) / (440 - 380);
            Green := 0.0;
            Blue  := 1.0
          END;
        440..489:
          BEGIN
            Red   := 0.0;
            Green := (Wavelength - 440) / (490 - 440);
            Blue  := 1.0
          END;
        490..509:
          BEGIN
            Red   := 0.0;
            Green := 1.0;
            Blue  := -(Wavelength - 510) / (510 - 490)
          END;
        510..579:
          BEGIN
            Red   := (Wavelength - 510) / (580 - 510);
            Green := 1.0;
            Blue  := 0.0
          END;
        580..644:
          BEGIN
            Red   := 1.0;
            Green := -(Wavelength - 645) / (645 - 580);
            Blue  := 0.0
          END;
        645..780:
          BEGIN
            Red   := 1.0;
            Green := 0.0;
            Blue  := 0.0
          END;
        ELSE
          Red   := 0.0;
          Green := 0.0;
          Blue  := 0.0
      END;
      // Let the intensity fall off near the vision limits
      CASE TRUNC(Wavelength) OF
        380..419:  factor := 0.3 + 0.7*(Wavelength - 380) / (420 - 380);
        420..700:  factor := 1.0;
        701..780:  factor := 0.3 + 0.7*(780 - Wavelength) / (780 - 700)
        ELSE       factor := 0.0
      END;
      R := Adjust(Red,   Factor);
      G := Adjust(Green, Factor);
      B := Adjust(Blue,  Factor)
    END {WavelengthToRGB}; 
    

    使用示例:

    数据集在 10..65,000,000 范围内。而这个特定数据点的值为 638,328:

    color = DataPointToColor(638328, 10, 65000000);
    

    【讨论】:

    • 这是个好主意。唯一的问题是必须不均匀地压缩到波长的转换。即,如果值的范围很大,我们不希望较小值的十进制值。他们仍然需要被代表。
    • @Richard:你必须决定什么是不平衡的。例如如果整个世界都处于冰冻之中,除了安哥拉的一个小镇,它太热而无法居住:那么一切都应该是蓝色的,除了那个应该是红色的点。当然,除非您想夸大较低范围内的值。您将不得不决定要进行哪种撒谎,以便最好地代表您的数据。如果源数据不是线性的(如您所建议的),那么一个常见的技巧是使用对数刻度;在绘制它们之前对这些值应用自然对数 ln(value)。
    【解决方案3】:

    颜色条的功能

    // value between 0 and 1 (percent)   
    function color(value) {
        var RGB = {R:0,G:0,B:0};
    
        // y = mx + b
        // m = 4
        // x = value
        // y = RGB._
        if (0 <= value && value <= 1/8) {
            RGB.R = 0;
            RGB.G = 0;
            RGB.B = 4*value + .5; // .5 - 1 // b = 1/2
        } else if (1/8 < value && value <= 3/8) {
            RGB.R = 0;
            RGB.G = 4*value - .5; // 0 - 1 // b = - 1/2
            RGB.B = 1; // small fix
        } else if (3/8 < value && value <= 5/8) {
            RGB.R = 4*value - 1.5; // 0 - 1 // b = - 3/2
            RGB.G = 1;
            RGB.B = -4*value + 2.5; // 1 - 0 // b = 5/2
        } else if (5/8 < value && value <= 7/8) {
            RGB.R = 1;
            RGB.G = -4*value + 3.5; // 1 - 0 // b = 7/2
            RGB.B = 0;
        } else if (7/8 < value && value <= 1) {
            RGB.R = -4*value + 4.5; // 1 - .5 // b = 9/2
            RGB.G = 0;
            RGB.B = 0;
        } else {    // should never happen - value > 1
            RGB.R = .5;
            RGB.G = 0;
            RGB.B = 0;
        }
    
        // scale for hex conversion
        RGB.R *= 15;
        RGB.G *= 15;
        RGB.B *= 15;
    
        return Math.round(RGB.R).toString(16)+''+Math.round(RGB.G).toString(16)+''+Math.round(RGB.B).toString(16);
    }
    

    【讨论】:

      【解决方案4】:

      根据 Chris H 提供的图片,您可以将 rgb 值建模为:

      r = min(max(0, 1.5-abs(1-4*(val-0.5))),1);
      g = min(max(0, 1.5-abs(1-4*(val-0.25))),1);
      b = min(max(0, 1.5-abs(1-4*val)),1);
      

      【讨论】:

        【解决方案5】:

        在不知道值的范围的情况下,您无法想出一个有意义的函数,将任意范围的正整数映射到热图类型的颜色范围。

        我认为您必须至少检查一次数据才能获得最小值/最大值或提前了解它们。一旦你有了它,你就可以适当地标准化并使用任意数量的配色方案。最简单的解决方案是指定“色调”之类的内容并将HSV 转换为 RGB。

        【讨论】:

        • 没有数学公式或算法可以使用最小/最大值作为输入来规范化一组数据吗?我认为我们需要一些对数函数,或者类似的东西。我们不希望在热图中丢失某些值,因为它们与其他值非常不同。例如,如果我们有一个值 20 和一个值 1M,我们希望两者都被表示,而不是让 20 显示为 0。
        • 然后查看对数分布或直方图均衡
        • 恐怕这超出了我的范围。 . .
        【解决方案6】:

        继续 Ian Boyd 的出色回答,我需要一组可区分的颜色来构建热图。诀窍是找到一种方法来区分接近的颜色,我找到了一个解决方案,通过转换为 HSV 并根据值改变 V,在颜色范围的中间稍微强调以带出黄色和橙色。

        代码如下:

        Imports System.Drawing
        Imports RGBHSV
        
        Module HeatToColour_
        
            ' Thanks to Ian Boyd's excellent post here:
            ' http://stackoverflow.com/questions/2374959/algorithm-to-convert-any-positive-integer-to-an-rgb-value
        
            Private Const MinVisibleWaveLength As Double = 450.0
            Private Const MaxVisibleWaveLength As Double = 700.0
            Private Const Gamma As Double = 0.8
            Private Const IntensityMax As Integer = 255
        
            Function HeatToColour(ByVal value As Double, ByVal MinValue As Double, ByVal MaxValues As Double) As System.Drawing.Color
        
                Dim wavelength As Double
                Dim Red As Double
                Dim Green As Double
                Dim Blue As Double
                Dim Factor As Double
                Dim scaled As Double
        
                scaled = (value - MinValue) / (MaxValues - MinValue)
        
                wavelength = scaled * (MaxVisibleWaveLength - MinVisibleWaveLength) + MinVisibleWaveLength
        
                Select Case Math.Floor(wavelength)
        
                    Case 380 To 439
                        Red = -(wavelength - 440) / (440 - 380)
                        Green = 0.0
                        Blue = 1.0
        
                    Case 440 To 489
                        Red = 0.0
                        Green = (wavelength - 440) / (490 - 440)
                        Blue = 1.0
        
                    Case 490 To 509
                        Red = 0.0
                        Green = 1.0
                        Blue = -(wavelength - 510) / (510 - 490)
        
                    Case 510 To 579
                        Red = (wavelength - 510) / (580 - 510)
                        Green = 1.0
                        Blue = 0.0
        
                    Case 580 To 644
                        Red = 1.0
                        Green = -(wavelength - 645) / (645 - 580)
                        Blue = 0.0
        
                    Case 645 To 780
                        Red = 1.0
                        Green = 0.0
                        Blue = 0.0
        
                    Case Else
                        Red = 0.0
                        Green = 0.0
                        Blue = 0.0
        
                End Select
        
                ' Let the intensity fall off near the vision limits
                Select Case Math.Floor(wavelength)
                    Case 380 To 419
                        Factor = 0.3 + 0.7 * (wavelength - 380) / (420 - 380)
                    Case 420 To 700
                        Factor = 1.0
                    Case 701 To 780
                        Factor = 0.3 + 0.7 * (780 - wavelength) / (780 - 700)
                    Case Else
                        Factor = 0.0
                End Select
        
                Dim R As Integer = Adjust(Red, Factor)
                Dim G As Integer = Adjust(Green, Factor)
                Dim B As Integer = Adjust(Blue, Factor)
        
                Dim result As Color = System.Drawing.Color.FromArgb(255, R, G, B)
                Dim resulthsv As New HSV
                resulthsv = ColorToHSV(result)
                resulthsv.Value = 0.7 + 0.1 * scaled + 0.2 * Math.Sin(scaled * Math.PI)
        
                result = HSVToColour(resulthsv)
        
                Return result
        
            End Function
            Private Function Adjust(ByVal Colour As Double, ByVal Factor As Double) As Integer
                If Colour = 0 Then
                    Return 0
                Else
                    Return Math.Round(IntensityMax * Math.Pow(Colour * Factor, Gamma))
                End If
            End Function
        
        End Module
        
        Imports System.Drawing
        Public Module RGBHSV
        
            Public Class HSV
                Sub New()
                    Hue = 0
                    Saturation = 0
                    Value = 0
                End Sub
                Public Sub New(ByVal H As Double, ByVal S As Double, ByVal V As Double)
                    Hue = H
                    Saturation = S
                    Value = V
                End Sub
                Public Hue As Double
                Public Saturation As Double
                Public Value As Double
            End Class
        
            Public Function ColorToHSV(ByVal color As Color) As HSV
                Dim max As Integer = Math.Max(color.R, Math.Max(color.G, color.B))
                Dim min As Integer = Math.Min(color.R, Math.Min(color.G, color.B))
                Dim result As New HSV
                With result
                    .Hue = color.GetHue()
                    .Saturation = If((max = 0), 0, 1.0 - (1.0 * min / max))
                    .Value = max / 255.0
                End With
                Return result
            End Function
        
            Public Function HSVToColour(ByVal hsv As HSV) As Color
                Dim hi As Integer
                Dim f As Double
        
                With hsv
                    hi = Convert.ToInt32(Math.Floor(.Hue / 60)) Mod 6
                    f = .Hue / 60 - Math.Floor(.Hue / 60)
                    .Value = .Value * 255
                    Dim v As Integer = Convert.ToInt32(.Value)
                    Dim p As Integer = Convert.ToInt32(.Value * (1 - .Saturation))
                    Dim q As Integer = Convert.ToInt32(.Value * (1 - f * .Saturation))
                    Dim t As Integer = Convert.ToInt32(.Value * (1 - (1 - f) * .Saturation))
        
                    If hi = 0 Then
                        Return Color.FromArgb(255, v, t, p)
                    ElseIf hi = 1 Then
                        Return Color.FromArgb(255, q, v, p)
                    ElseIf hi = 2 Then
                        Return Color.FromArgb(255, p, v, t)
                    ElseIf hi = 3 Then
                        Return Color.FromArgb(255, p, q, v)
                    ElseIf hi = 4 Then
                        Return Color.FromArgb(255, t, p, v)
                    Else
                        Return Color.FromArgb(255, v, p, q)
                    End If
                End With
            End Function
        
        End Module
        

        以及由此产生的热图,显示 EEC 国家/地区的人均 GDP:

        【讨论】:

          【解决方案7】:

          这个答案可能有点晚了。我正在显示一些环境数据,并且需要相对于数据集的最大值和最小值(或作为最大值和最小值传递给函数的值)将结果条从绿色变为红色。无论如何,下面实现了. 可以很容易地从蓝色变为红色,我想。

          // scale colour temp relatively
          
          function getColourTemp(maxVal, minVal, actual) {
              var midVal = (maxVal - minVal)/2;
              var intR;
              var intG;
              var intB = Math.round(0);
          
              if (actual >= midVal){
                   intR = 255;
                   intG = Math.round(255 * ((maxVal - actual) / (maxVal - midVal)));
              }
              else{
                  intG = 255;
                  intR = Math.round(255 * ((actual - minVal) / (midVal - minVal)));
              }
          
              return to_rgb(intR, intG, intB);
          }
          

          【讨论】:

            【解决方案8】:

            伙计,您可能会使用 YUV 颜色空间,仅出于演示目的将其转换为 RGB。

            【讨论】:

              【解决方案9】:

              有点晚了,但我尝试做同样的事情,发现我可以将 HSV 修改为 RGB 以获得类似的结果。它类似于波长方法,但不需要先转换为波长。只需将 H 替换为您的值(假设值介于 0 和 1 之间),并将 S 和 V 固定为 1。我发现此处的 HSVtoRGB 示例非常有用:

              http://www.cs.rit.edu/~ncs/color/t_convert.html

              但是,我不得不换行

              h /= 60;
              i = floor ( h );
              

              到

              h *= 5;
              i = (int) h;
              

              获得贯穿整个频谱的输出。

              其他资源: http://www.easyrgb.com/index.php?X=MATH&H=21#text21

              【讨论】:

                【解决方案10】:

                简单算法

                // given a max and min value
                float red,green,blue;
                float range=max-min;
                float mid=(max+min)/2.0;
                
                //foreach value
                    red = (value[ii]-mid)/range;            
                    if (red>0.0) {
                        //above mid = red-green
                        blue=0.0;
                        green = 1.0-red;
                    } else {
                        // lower half green-blue
                        blue=-red;
                        green = 1.0-blue;
                        red=0.0;
                    }
                
                }
                

                更复杂:
                如果您的范围是几百万,但大多数都在 0 左右,您希望对其进行缩放,以便上例中的“红色”是与中点距离的对数。如果值为 +/-

                ,则代码会稍微复杂一些
                // assume equally distributed around 0 so max is the largest (or most negative number)
                float range = log(fabs(max));
                float mid=0.0
                
                // foreach value
                if (value[ii] > 0.0 ) {
                    // above mid = red-green
                    red = log(value[ii])/range;
                    blue=0.0;
                    green = 1.0 - red;
                } else {
                    // below mid = green-blue
                    blue=-log(value[ii])/range;
                    green = 1.0 - blue;
                   red = 0.0;
                }
                

                注意 - 我没有测试过这段代码,只是想出一些想法!

                【讨论】:

                • Martin,我刚刚在 excel 中尝试过。 . .没运气。 rgb 值都在 0.5 或 0 左右,尤其是值差异很大。
                • 是的 - 它适用于简单的案例,只是搜索引擎的大脑转储。如果您的所有值都在 0.5 左右且有异常值,您可以简单地记录 value-mid 的对数吗?
                • 不知道你的意思只是搜索引擎的大脑转储。 . .我认为这行不通。一些红色值是负数,因此日志在那里不起作用。我们确实需要对范围内的数字进行对数压缩。
                • 对不起 - 意思是我在这里发布了简单的算法,以便我可以将人们指向它,或者他们会在搜索中找到它。
                猜你喜欢
                • 1970-01-01
                • 2016-02-20
                • 2013-06-15
                • 2014-07-22
                • 2013-10-25
                • 2020-01-04
                • 1970-01-01
                • 1970-01-01
                • 2014-03-20
                相关资源
                最近更新 更多