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