Алгоритм для преобразования любого натурального числа в значение RGB - PullRequest
21 голосов
/ 04 марта 2010

У нас есть тепловая карта, которую мы хотим отобразить. Числа, которые будут составлять отображаемые значения, неизвестны (за исключением того, что они будут положительными целыми числами). Диапазон чисел также неизвестен (опять же, за исключением того, что они будут положительными целыми числами). Диапазон может быть между 0 и 200 или 578 и 1M или чем-то еще. Это зависит от данных, которые неизвестны.

Мы хотим взять неизвестный диапазон натуральных чисел и превратить его в масштабированный (сжатый) диапазон для отображения со значениями RGB в тепловой карте. Я надеюсь это имеет смысл. Спасибо!

Хочу уточнить, что минимальные / максимальные значения нужно «подключить» к форумчанам.

Ответы [ 10 ]

21 голосов
/ 04 марта 2010

Сначала нужно найти диапазон этих значений, чтобы получить минимальное и максимальное значения. Затем вам нужно создать цветовую шкалу, как в строке ниже этого изображения. Вы можете поэкспериментировать с различными функциями, чтобы отобразить целое число в RGB. Вам нужно 3 функции R (X), G (X), B (X). Глядя на изображение ниже, оно выглядит как пики B (X) в середине, пики R (X) в конце, а зеленый - где-то еще. Если вы уверены, что никогда не получите два (RGB) для некоторого значения X, то вы получите свое преобразование.

alt text
(источник: globalwarmingart.com )

EDIT: Если подумать, вы можете взять образец круга вокруг пространства YUV. альтернативный текст http://www.biocrawler.com/w/images/e/ec/Yuv.png

Или даже просто скачайте цветную полосу высокого разрешения и попробуйте ее.

РЕДАКТИРОВАТЬ 2: Я только что столкнулся с генерацией цветовых полос и вспомнил код цветовой полосы MATLAB / Octave. Я нанес их на карту и получил следующее изображение. alt text

11 голосов
/ 04 марта 2010

Вы хотите преобразовать свои значения данных в частоту света:

  • меньшая длина волны = холодные цвета = голубоватый
  • большая длина волны = теплые цвета = красный

Частоты видимого света изменяются от 350 нм (фиолетовый) до 650 нм (красный):

alt text
(источник: 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;

И функция , которую я нашел в интернете , которая преобразует длину волны в 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);
7 голосов
/ 08 июня 2012

Функция для цветовой шкалы

// 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 голосов
/ 18 мая 2015

Исходя из рисунка, предоставленного Крисом Х, вы можете смоделировать значения 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);
2 голосов
/ 21 сентября 2013

Этот ответ, вероятно, немного опоздал на вечеринку. Я отображаю некоторые данные об окружающей среде, и мне нужно покрасить результирующие столбцы от зеленого до красного относительно максимума и минимума набора данных (или какие значения всегда передаются как максимальные и минимальные значения функции. В любом случае, приведенное ниже выполнение Я могу подумать, что достаточно легко изменить цвет с синего на красный.

// 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);
}
2 голосов
/ 28 июля 2011

Продолжая превосходный ответ Яна Бойда, мне понадобился различимый набор цветов для построения тепловой карты.Уловка состояла в том, чтобы найти способ дифференцировать близкие цвета, и я нашел решение, преобразовав HSV и изменив V в соответствии со значением, с небольшим акцентом в середине цветового диапазона, чтобы выделить желтые и апельсины.

Вот код:

Imports System.Drawing
Imports RGBHSV

Module HeatToColour_

    ' Thanks to Ian Boyd's excellent post here:
    ' /2081122/algoritm-dlya-preobrazovaniya-lybogo-naturalnogo-chisla-v-znachenie-rgb

    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

и полученная тепловая карта, показывающая ВВП на душу населения для стран ЕЭС: GDP/Capita, EEC

2 голосов
/ 04 марта 2010

Не зная диапазона значений, вы мало что можете сделать, чтобы придумать значимую функцию, отображающую произвольный диапазон натуральных чисел в диапазон цветов типа тепловой карты.

Я думаю, вам придется хотя бы один раз просмотреть ваши данные, чтобы получить минимальное / максимальное значение или узнать их заранее. Если у вас есть это, вы можете нормализовать и использовать любое количество цветовых схем. Простейшим решением было бы указать что-то вроде «hue» и конвертировать из HSV в RGB.

0 голосов
/ 13 ноября 2011

Немного поздно, но я пытался сделать то же самое и обнаружил, что могу изменить 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

0 голосов
/ 04 марта 2010

человек, вы, вероятно, могли бы использовать цветовое пространство YUV и только для демонстрационных целей преобразовать его в RGB.

0 голосов
/ 04 марта 2010

Простой алгоритм

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

примечание - я не проверял этот код, просто раскручивал идеи!

...