Algorithm to convert any positive integer to an RGB value

后端 未结 10 1291
没有蜡笔的小新
没有蜡笔的小新 2020-12-04 13:49

We have a heatmap we want to display. The numbers that will make up the values being displayed are unknown (except that they will be positive integers). The range of numbers

相关标签:
10条回答
  • 2020-12-04 14:01

    You need to first find the range of those values to get the min and max. Then you need to create a colour scale like the bar below this image. You can experiment with different functions to map an integer to an RGB. You need 3 functions R(X), G(X), B(X). Looking at the image below it loks like B(X) peaks in the middle, R(X) peaks at the end and green is somewhere else. As long as you make sure that you never get two (RGBs) for some value of X then you've got your conversion.


    (source: globalwarmingart.com)

    EDIT: Come to think of it you could sample some unit circle around YUV space. alt text http://www.biocrawler.com/w/images/e/ec/Yuv.png

    Or even just download a high-res colour bar and sample that.

    EDIT 2: I was just faced with color bar generation and remembered the MATLAB/Octave colorbar code. I plotted their data and got the following image. alt text

    0 讨论(0)
  • 2020-12-04 14:05

    Continuing from Ian Boyd's excellent answer, I needed a distinguishable set of colours to build a heatmap. The trick was to find a way to differentiate close colours and I found a solution by converting to HSV and varying the V according to the value, with a little emphasis in the middle of the colour range to bring out the yellows and oranges.

    Here's the code:

    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
    

    and a resulting heatmap, showing GDP per capita for the countries in the EEC: GDP/Capita, EEC

    0 讨论(0)
  • 2020-12-04 14:13

    Simple algorithm

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

    More complex:
    If your range is a few million but most are around 0 you want to scale it so that 'red' in the above example is the log of the distance from the midpoint. The cod eis a little more complicated if the values are +/-

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

    note - I haven't tested this code, just spinning ideas!

    0 讨论(0)
  • 2020-12-04 14:16

    Function for colorbar

    // 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);
    }
    
    0 讨论(0)
  • 2020-12-04 14:16

    Going off of the picture provided by Chris H, you can model the rgb values as:

    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);
    
    0 讨论(0)
  • 2020-12-04 14:19

    This answer is probably a little late to the party. I'm displaying some environmental data, and need to colour the resulting bars from green to red relative to the max and min of the data set (or which ever values are passed as max and min to the function. Anyway, the below accomplishes that. Can be changed for blue to red fairly easily enough, I would think.

    // 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);
    }
    
    0 讨论(0)
提交回复
热议问题