]> git.sesse.net Git - vlc/blob - modules/video_filter/atmo/AtmoCalculations.cpp
Use var_InheritString for --decklink-video-connection.
[vlc] / modules / video_filter / atmo / AtmoCalculations.cpp
1 /*
2  * calculations.c: calculations needed by the input devices
3  *
4  * See the README file for copyright information and how to reach the author.
5  *
6  * $Id$
7  */
8 #include <stdlib.h>
9 #include <string.h>
10 #include <stdio.h>
11
12 #include "AtmoDefs.h"
13
14 #if defined(WIN32)
15 #   include <windows.h>
16 #endif
17
18 #include "AtmoCalculations.h"
19 #include "AtmoConfig.h"
20 #include "AtmoZoneDefinition.h"
21
22
23 // set accuracy of color calculation
24 #define h_MAX   255
25 #define s_MAX   255
26 #define v_MAX   255
27
28 // macro
29 #define POS_DIV(a, b)  ( (a)/(b) + ( ((a)%(b) >= (b)/2 ) ? 1 : 0) )
30
31
32 CAtmoColorCalculator::CAtmoColorCalculator(CAtmoConfig *pAtmoConfig)
33 {
34   m_pAtmoConfig = pAtmoConfig;
35   m_Weight                = NULL;
36   m_hue_hist              = NULL;
37   m_windowed_hue_hist     = NULL;
38   m_most_used_hue_last    = NULL;
39   m_most_used_hue         = NULL;
40   m_sat_hist              = NULL;
41   m_windowed_sat_hist     = NULL;
42   m_most_used_sat         = NULL;
43   m_Zone_Weights          = NULL;
44   m_average_v             = NULL;
45   m_average_counter       = NULL;
46
47   m_LastEdgeWeighting      = -1;
48   m_LastWidescreenMode     = -1;
49   m_LastLayout_TopCount    = -1;
50   m_LastLayout_BottomCount = -1;
51   m_LastLayout_LRCount     = -1;
52   m_LastNumZones           = -1;
53 }
54
55 CAtmoColorCalculator::~CAtmoColorCalculator(void)
56 {
57   delete[] m_Weight;
58   delete[] m_hue_hist;
59   delete[] m_windowed_hue_hist;
60   delete[] m_most_used_hue_last;
61   delete[] m_most_used_hue;
62   delete[] m_sat_hist;
63   delete[] m_windowed_sat_hist;
64   delete[] m_most_used_sat;
65   delete[] m_Zone_Weights;
66   delete[] m_average_v;
67   delete[] m_average_counter;
68 }
69
70 void CAtmoColorCalculator::UpdateParameters()
71 {
72   // Zonen Definition neu laden
73   // diverse Vorberechnungen neu ausführen
74   // Speicherbuffer neu allokieren!
75 }
76
77 void CAtmoColorCalculator::FindMostUsed(int AtmoSetup_NumZones,int *most_used,long int *windowed_hist)
78 {
79   memset(most_used, 0, sizeof(int) * AtmoSetup_NumZones);
80
81
82   for (int zone = 0; zone < AtmoSetup_NumZones; zone++)
83   {
84     int value = 0;
85     // walk trough histogram
86     for (int i = 0; i < s_MAX+1; i++) // assume s_MAX = h_MAX = v_Max
87     {
88       // if new value bigger then old one
89       int tmp = *windowed_hist;  // windowed_hist[zone * (s_MAX+1) + i];
90       // if (w_sat_hist[channel][i] > value)
91       if (tmp > value)
92       {
93         // remember index
94         most_used[zone] = i;
95         // and value
96         value = tmp;
97       }
98       windowed_hist++;
99     }
100   }
101 }
102
103 pColorPacket CAtmoColorCalculator::AnalyzeHSV(tHSVColor *HSV_Img)
104 {
105   int i; // counter
106
107   int AtmoSetup_EdgeWeighting  = m_pAtmoConfig->getLiveView_EdgeWeighting();
108   int AtmoSetup_WidescreenMode = m_pAtmoConfig->getLiveView_WidescreenMode();
109   int AtmoSetup_DarknessLimit  = m_pAtmoConfig->getLiveView_DarknessLimit();
110   int AtmoSetup_BrightCorrect  = m_pAtmoConfig->getLiveView_BrightCorrect();
111   int AtmoSetup_SatWinSize     = m_pAtmoConfig->getLiveView_SatWinSize();
112   int AtmoSetup_NumZones       = m_pAtmoConfig->getZoneCount();
113   tHSVColor *temp_Img;
114
115   if(AtmoSetup_NumZones != m_LastNumZones)
116   {
117      delete[] m_Weight;
118      delete[] m_hue_hist;
119      delete[] m_windowed_hue_hist;
120      delete[] m_most_used_hue_last;
121      delete[] m_most_used_hue;
122      delete[] m_sat_hist;
123      delete[] m_windowed_sat_hist;
124      delete[] m_most_used_sat;
125      delete[] m_Zone_Weights;
126      delete[] m_average_v;
127      delete[] m_average_counter;
128
129      m_Weight              = new int[AtmoSetup_NumZones * IMAGE_SIZE];
130      m_Zone_Weights        = new int*[AtmoSetup_NumZones];
131      for(int i = 0; i < AtmoSetup_NumZones; i++)
132          m_Zone_Weights[i] = &m_Weight[i * IMAGE_SIZE];
133
134      m_hue_hist            = new long int[(h_MAX+1) * AtmoSetup_NumZones];
135      m_windowed_hue_hist   = new long int[(h_MAX+1) * AtmoSetup_NumZones];
136
137      m_most_used_hue_last  = new int[AtmoSetup_NumZones];
138      m_most_used_hue       = new int[AtmoSetup_NumZones];
139      memset( m_most_used_hue_last, 0, sizeof(int) * AtmoSetup_NumZones);
140
141      m_sat_hist           = new long int[(s_MAX+1) * AtmoSetup_NumZones];
142      m_windowed_sat_hist  = new long int[(s_MAX+1) * AtmoSetup_NumZones];
143      m_most_used_sat      = new int[AtmoSetup_NumZones];
144
145      m_average_v         = new long int[AtmoSetup_NumZones];
146      m_average_counter   = new int[AtmoSetup_NumZones];
147
148      m_LastNumZones = AtmoSetup_NumZones;
149   }
150
151
152   // calculate only if setup has changed
153   if ((AtmoSetup_EdgeWeighting != m_LastEdgeWeighting) ||
154       (AtmoSetup_WidescreenMode != m_LastWidescreenMode) ||
155       (m_pAtmoConfig->getZonesTopCount() != m_LastLayout_TopCount) ||
156       (m_pAtmoConfig->getZonesBottomCount() != m_LastLayout_BottomCount) ||
157       (m_pAtmoConfig->getZonesLRCount() !=  m_LastLayout_LRCount) ||
158       (m_pAtmoConfig->m_UpdateEdgeWeightningFlag != 0)
159      )
160   {
161
162       for(i = 0 ;i < AtmoSetup_NumZones; i++) {
163           CAtmoZoneDefinition *pZoneDef = m_pAtmoConfig->getZoneDefinition(i);
164           if(pZoneDef)
165           {
166              pZoneDef->UpdateWeighting(m_Zone_Weights[i],
167                                                               AtmoSetup_WidescreenMode,
168                                                               AtmoSetup_EdgeWeighting);
169 #ifdef _debug_zone_weight_
170              char filename[128];
171              sprintf(filename, "zone_%d_gradient_debug.bmp",i);
172              pZoneDef->SaveZoneBitmap( filename );
173              sprintf(filename, "zone_%d_weight_%d_debug.bmp",i,AtmoSetup_EdgeWeighting);
174              pZoneDef->SaveWeightBitmap(filename, m_Zone_Weights[i] );
175 #endif
176           }
177
178      }
179      m_pAtmoConfig->m_UpdateEdgeWeightningFlag = 0;
180
181      m_LastEdgeWeighting      = AtmoSetup_EdgeWeighting;
182      m_LastWidescreenMode     = AtmoSetup_WidescreenMode;
183      m_LastLayout_TopCount    = m_pAtmoConfig->getZonesTopCount();
184      m_LastLayout_BottomCount = m_pAtmoConfig->getZonesBottomCount();
185      m_LastLayout_LRCount     = m_pAtmoConfig->getZonesLRCount();
186   }
187
188   AtmoSetup_DarknessLimit = AtmoSetup_DarknessLimit * 10;
189
190
191   /***************************************************************************/
192   /* Hue                                                                     */
193   /***************************************************************************/
194   /*----------------------------*/
195   /* hue histogram builtup      */
196   /*----------------------------*/
197   // HSV histogram
198   // long int hue_hist[CAP_MAX_NUM_ZONES][h_MAX+1];
199
200   // average brightness (value)
201   // m_average_v m_average_counter
202
203   // clean histogram --> calloc
204   memset(m_hue_hist, 0, sizeof(long int) * (h_MAX+1) * AtmoSetup_NumZones);
205   memset(m_average_v, 0, sizeof(long int) * AtmoSetup_NumZones);
206   memset(m_average_counter, 0, sizeof(int) * AtmoSetup_NumZones);
207
208   temp_Img = HSV_Img;
209   i = 0;
210   for (int row = 0; row < CAP_HEIGHT; row++)
211   {
212     for (int column = 0; column < CAP_WIDTH; column++)
213     {
214       // forget black bars: perform calculations only if pixel has some luminosity
215           if ((*temp_Img).v > AtmoSetup_DarknessLimit)
216       {
217         // builtup histogram for the x Zones of the Display
218         for (int zone = 0; zone < AtmoSetup_NumZones; zone++)
219         {
220           // Add weight to channel
221          // Weight(zone, pixel_nummer) m_Weight[((zone) * (IMAGE_SIZE)) + (pixel_nummer)]
222          // m_hue_hist[zone*(h_MAX+1) + HSV_Img[i].h] += m_Zone_Weights[zone][i] * HSV_Img[i].v;
223             m_hue_hist[zone*(h_MAX+1) + (*temp_Img).h] += m_Zone_Weights[zone][i] * temp_Img->v;
224
225             if(m_Zone_Weights[zone][i] > 0) {
226                m_average_v[zone] += temp_Img->v;
227                m_average_counter[zone]++;
228             }
229
230         }
231         // calculate brightness average
232       }
233       temp_Img++;
234       i++;
235     }
236   }
237
238   /*----------------------------*/
239   /* hue histogram windowing    */
240   /*----------------------------*/
241   // windowed HSV histogram
242   // long int w_hue_hist[CAP_MAX_NUM_ZONES][h_MAX+1]; -> m_windowed_hue_hist
243   // clean windowed histogram
244   memset(m_windowed_hue_hist, 0, sizeof(long int) * (h_MAX+1) * AtmoSetup_NumZones);
245   // steps in each direction; eg. 2 => -2 -1 0 1 2 windowing
246   int hue_windowsize = m_pAtmoConfig->getLiveView_HueWinSize();
247
248   for (i = 0; i < h_MAX+1; i++) // walk through histogram [0;h_MAX]
249   {
250     // windowing from -hue_windowsize -> +hue_windowsize
251     for (int mywin = -hue_windowsize; mywin < hue_windowsize+1; mywin++)
252     {
253       // addressed histogram candlestick
254       int myidx = i + mywin;
255
256       // handle beginning of windowing -> roll back
257       if (myidx < 0)     { myidx = myidx + h_MAX + 1; }
258
259       // handle end of windowing -> roll forward
260       if (myidx > h_MAX) { myidx = myidx - h_MAX - 1; }
261
262       // Apply windowing to all x zones
263       for (int zone = 0; zone < AtmoSetup_NumZones; zone++)
264       {
265         // apply lite triangular window design with gradient of 10% per discrete step
266         m_windowed_hue_hist[(zone * (h_MAX+1)) + i] += m_hue_hist[(zone * (h_MAX+1)) + myidx] * ((hue_windowsize+1)-abs(mywin)); // apply window
267       }
268     }
269   }
270
271   /*--------------------------------------*/
272   /* analyze histogram for most used hue  */
273   /*--------------------------------------*/
274   // index of last maximum
275   // static int most_used_hue_last[CAP_MAX_NUM_ZONES] = {0, 0, 0, 0, 0}; --> m_most_used_hue_last
276
277   // resulting hue for each channel
278   //int most_used_hue[CAP_MAX_NUM_ZONES]; --> m_most_used_hue
279
280   FindMostUsed(AtmoSetup_NumZones, m_most_used_hue, m_windowed_hue_hist);
281   for (int zone = 0; zone < AtmoSetup_NumZones; zone++)
282   {
283     float percent = (float)m_windowed_hue_hist[zone * (h_MAX+1) + m_most_used_hue_last[zone]] / (float)m_windowed_hue_hist[zone * (h_MAX+1) + m_most_used_hue[zone]];
284     if (percent > 0.93f) // less than 7% difference?
285         m_most_used_hue[zone] = m_most_used_hue_last[zone]; // use last index
286      else
287         m_most_used_hue_last[zone] = m_most_used_hue[zone];
288   }
289
290   /*
291   memset(m_most_used_hue, 0, sizeof(int) * AtmoSetup_NumZones);
292
293   for (int zone = 0; zone < AtmoSetup_NumZones; zone++)
294   {
295     long int value = 0;
296     for (i = 0; i < h_MAX+1; i++) // walk through histogram
297     {
298       long int tmp = m_windowed_hue_hist[ (zone * (h_MAX+1)) + i ];
299       if (tmp > value) // if new value bigger then old one
300       {
301         m_most_used_hue[zone] = i;     // remember index
302         value = tmp; // w_hue_hist[zone][i]; // and value
303       }
304     }
305
306     float percent = (float)m_windowed_hue_hist[zone * (h_MAX+1) + m_most_used_hue_last[zone]] / (float)value;
307     if (percent > 0.93f) // less than 7% difference?
308     {
309       m_most_used_hue[zone] = m_most_used_hue_last[zone]; // use last index
310     }
311
312     m_most_used_hue_last[zone] = m_most_used_hue[zone]; // save current index of most used hue
313   }
314   */
315
316   /***************************************************************************/
317   /* saturation                                                              */
318   /***************************************************************************/
319   // sat histogram
320   // long int sat_hist[CAP_MAX_NUM_ZONES][s_MAX+1];  -> m_sat_hist
321   // hue of the pixel we are working at
322   int pixel_hue = 0;
323   // clean histogram
324   memset(m_sat_hist, 0, sizeof(long int) * (s_MAX+1) * AtmoSetup_NumZones);
325
326   /*--------------------------------------*/
327   /* saturation histogram builtup         */
328   /*--------------------------------------*/
329   i = 0;
330   temp_Img = HSV_Img;
331   for (int row = 0; row < CAP_HEIGHT; row++)
332   {
333     for (int column = 0; column < CAP_WIDTH; column++)
334     {
335       // forget black bars: perform calculations only if pixel has some luminosity
336           if ((*temp_Img).v > AtmoSetup_DarknessLimit)
337       {
338         // find histogram position for pixel
339         pixel_hue = (*temp_Img).h;
340
341         // TODO:   brightness calculation(if we require it some time)
342
343         for (int zone = 0; zone < AtmoSetup_NumZones; zone++)
344         {
345           // only use pixel for histogram if hue is near most_used_hue
346           if ((pixel_hue > m_most_used_hue[zone] - hue_windowsize) &&
347               (pixel_hue < m_most_used_hue[zone] + hue_windowsize))
348           {
349             // build histogram
350             // sat_hist[channel][HSV_Img[i].s] += Weight[i].channel[channel] * HSV_Img[i].v;
351             m_sat_hist[zone * (s_MAX+1) + (*temp_Img).s ] += m_Zone_Weights[zone][i] * (*temp_Img).v;
352
353           }
354         }
355       }
356       i++;
357       temp_Img++;
358     }
359   }
360
361   /*--------------------------------------*/
362   /* saturation histogram windowing       */
363   /*--------------------------------------*/
364    // windowed HSV histogram
365    // long int w_sat_hist[CAP_MAX_NUM_ZONES][s_MAX+1]; --> m_windowed_sat_hist
366    // clean windowed histogram
367    memset(m_windowed_sat_hist, 0, sizeof(long int) * (s_MAX+1) * AtmoSetup_NumZones);
368    // steps in each direction; eg. 2 => -2 -1 0 1 2 windowing
369    int sat_windowsize = AtmoSetup_SatWinSize;
370
371    // walk through histogram [0;h_MAX]
372    for (i = 0; i < s_MAX + 1; i++)
373    {
374      // windowing from -hue_windowsize -> +hue_windowsize
375      for (int mywin = -sat_windowsize; mywin < sat_windowsize+1; mywin++)
376      {
377        // addressed histogram candlestick
378        int myidx = i + mywin;
379
380        // handle beginning of windowing -> roll back
381        if (myidx < 0)     { myidx = myidx + s_MAX + 1; }
382
383        // handle end of windowing -> roll forward
384        if (myidx > h_MAX) { myidx = myidx - s_MAX - 1; }
385
386        for (int zone = 0; zone < AtmoSetup_NumZones; zone++)
387        {
388          /*
389             apply lite triangular window design with
390             gradient of 10% per discrete step
391          */
392          /*
393          w_sat_hist[channel][i] += sat_hist[channel][myidx] *
394                                   ((sat_windowsize+1)-abs(mywin)); // apply window
395          */
396          m_windowed_sat_hist[zone * (s_MAX+1) + i] += m_sat_hist[zone* (h_MAX+1) + myidx] *
397                                   ((sat_windowsize+1)-abs(mywin)); // apply window
398        }
399      }
400    }
401
402   /*--------------------------------------*/
403   /* analyze histogram for most used sat  */
404   /*--------------------------------------*/
405    // resulting sat (most_used_hue) for each channel
406   // int most_used_sat[CAP_MAX_NUM_ZONES];->m_most_used_sat
407
408   FindMostUsed(AtmoSetup_NumZones, m_most_used_sat, m_windowed_sat_hist);
409   /*
410   memset(m_most_used_sat, 0, sizeof(int) * AtmoSetup_NumZones);
411
412   for (int zone = 0; zone < AtmoSetup_NumZones; zone++)
413   {
414     int value = 0;
415     // walk trough histogram
416     for (i = 0; i < s_MAX+1; i++)
417     {
418       // if new value bigger then old one
419       int tmp = m_windowed_sat_hist[zone * (s_MAX+1) + i];
420       // if (w_sat_hist[channel][i] > value)
421       if (tmp > value)
422       {
423         // remember index
424         m_most_used_sat[zone] = i;
425         // and value
426         value = tmp;
427       }
428     }
429   }
430   */
431
432
433   /*----------------------------------------------------------*/
434   /* calculate average brightness within HSV image            */
435   /* uniform Brightness for all channels is calculated        */
436   /*----------------------------------------------------------*/
437   /* code integrated into "hue histogram builtup" to save some looping time!
438   int l_counter = 0;
439   // average brightness (value)
440   long int value_avg = 0;
441   i = 0;
442   for (int row = 0; row < CAP_HEIGHT; row++)
443   {
444     for (int column = 0; column < CAP_WIDTH; column++)
445     {
446       // find average value: only use bright pixels for luminance average
447           if (HSV_Img[i].v > AtmoSetup_DarknessLimit)
448       {
449         // build brightness average
450         value_avg += HSV_Img[i].v;
451         l_counter++;
452       }
453       i++;
454     }
455   }
456   // calculate brightness average
457   if (l_counter > 0) { value_avg = value_avg / l_counter; }
458     else { value_avg = AtmoSetup_DarknessLimit; }
459
460   */
461
462
463   /*----------------------------*/
464   /* adjust and copy results    */
465   /*----------------------------*/
466   tHSVColor hsv_pixel;
467   // storage container for resulting RGB values
468   pColorPacket output_colors;
469   AllocColorPacket(output_colors, AtmoSetup_NumZones);
470
471   // adjust brightness
472 //  int new_value = (int) ((float)value_avg * ((float)AtmoSetup_BrightCorrect / 100.0));
473 //  if (new_value > 255) new_value = 255;  // ensure brightness isn't set too high
474 //  hsv_pixel.v = (unsigned char)new_value;
475
476   /*
477   // calculate brightness average
478   for(int zone = 0; zone < AtmoSetup_NumZones; zone++) {
479       if(m_average_counter[zone] > 0)
480           m_average_v[zone] = m_average_v[zone] / m_average_counter[zone]
481       else
482           m_average_v[zone] = AtmoSetup_DarknessLimit;
483   }
484
485   */
486
487   for (int zone = 0; zone < AtmoSetup_NumZones; zone++)
488   {
489     if(m_average_counter[zone] > 0)
490        m_average_v[zone] = m_average_v[zone] / m_average_counter[zone];
491     else
492        m_average_v[zone] = AtmoSetup_DarknessLimit;
493
494     m_average_v[zone] = (int)((float)m_average_v[zone] * ((float)AtmoSetup_BrightCorrect / 100.0));
495
496     hsv_pixel.v = (unsigned char)ATMO_MAX(ATMO_MIN(m_average_v[zone],255),0);
497     hsv_pixel.h = m_most_used_hue[zone];
498     hsv_pixel.s = m_most_used_sat[zone];
499
500     // convert back to rgb
501     output_colors->zone[zone] = HSV2RGB(hsv_pixel);
502   }
503
504
505   return output_colors;
506 }
507
508 tHSVColor RGB2HSV(tRGBColor color)
509 {
510  int min, max, delta;
511  int r, g, b;
512  int h = 0;
513  tHSVColor hsv;
514
515  r = color.r;
516  g = color.g;
517  b = color.b;
518
519  min = ATMO_MIN(ATMO_MIN(r, g), b);
520  max = ATMO_MAX(ATMO_MAX(r, g), b);
521
522  delta = max - min;
523
524  hsv.v = (unsigned char) POS_DIV( max*v_MAX, 255 );
525
526  if (delta == 0) // This is a gray, no chroma...
527  {
528    h = 0;        // HSV results = 0 / 1
529    hsv.s = 0;
530  }
531  else // Chromatic data...
532  {
533    hsv.s = (unsigned char) POS_DIV( (delta*s_MAX) , max );
534
535    int dr = (max - r) + 3*delta;
536    int dg = (max - g) + 3*delta;
537    int db = (max - b) + 3*delta;
538    int divisor = 6*delta;
539
540    if (r == max)
541    {
542      h = POS_DIV(( (db - dg) * h_MAX ) , divisor);
543    }
544    else if (g == max)
545    {
546      h = POS_DIV( ((dr - db) * h_MAX) , divisor) + (h_MAX/3);
547    }
548    else if (b == max)
549    {
550      h = POS_DIV(( (dg - dr) * h_MAX) , divisor) + (h_MAX/3)*2;
551    }
552
553    if ( h < 0 )     { h += h_MAX; }
554    if ( h > h_MAX ) { h -= h_MAX; }
555  }
556  hsv.h = (unsigned char)h;
557
558  return hsv;
559 }
560
561 tRGBColor HSV2RGB(tHSVColor color)
562 {
563  tRGBColor rgb = {0, 0, 0};
564
565  float h = (float)color.h/(float)h_MAX;
566  float s = (float)color.s/(float)s_MAX;
567  float v = (float)color.v/(float)v_MAX;
568
569  if (s == 0)
570  {
571    rgb.r = (int)((v*255.0)+0.5);
572    rgb.g = rgb.r;
573    rgb.b = rgb.r;
574  }
575  else
576  {
577    h = h * 6.0f;
578    if (h == 6.0) { h = 0.0; }
579    int i = (int)h;
580
581    float f = h - i;
582    float p = v*(1.0f-s);
583    float q = v*(1.0f-(s*f));
584    float t = v*(1.0f-(s*(1.0f-f)));
585
586    if (i == 0)
587    {
588      rgb.r = (int)((v*255.0)+0.5);
589      rgb.g = (int)((t*255.0)+0.5);
590      rgb.b = (int)((p*255.0)+0.5);
591    }
592    else if (i == 1)
593    {
594      rgb.r = (int)((q*255.0)+0.5);
595      rgb.g = (int)((v*255.0)+0.5);
596      rgb.b = (int)((p*255.0)+0.5);
597    }
598    else if (i == 2)
599    {
600      rgb.r = (int)((p*255.0)+0.5);
601      rgb.g = (int)((v*255.0)+0.5);
602      rgb.b = (int)((t*255.0)+0.5);
603    }
604    else if (i == 3)
605    {
606      rgb.r = (int)((p*255.0)+0.5);
607      rgb.g = (int)((q*255.0)+0.5);
608      rgb.b = (int)((v*255.0)+0.5);
609    }
610    else if (i == 4)
611    {
612      rgb.r = (int)((t*255.0)+0.5);
613      rgb.g = (int)((p*255.0)+0.5);
614      rgb.b = (int)((v*255.0)+0.5);
615    }
616    else
617    {
618      rgb.r = (int)((v*255.0)+0.5);
619      rgb.g = (int)((p*255.0)+0.5);
620      rgb.b = (int)((q*255.0)+0.5);
621    }
622  }
623  return rgb;
624 }