src/lib/memgfx.c 1.49

1.49 2009/06/19 19:14:42 mikep
added RGB->HSL/HSV->RGB conversion and transformation code, plus test cases in colorConv program
Index: src/lib/memgfx.c
===================================================================
RCS file: /projects/compbio/cvsroot/kent/src/lib/memgfx.c,v
retrieving revision 1.48
retrieving revision 1.49
diff -b -B -U 1000000 -r1.48 -r1.49
--- src/lib/memgfx.c	20 Feb 2008 00:42:33 -0000	1.48
+++ src/lib/memgfx.c	19 Jun 2009 19:14:42 -0000	1.49
@@ -1,677 +1,931 @@
 /* memgfx - routines for drawing on bitmaps in memory.
  * Currently limited to 256 color bitmaps. 
  *
  * This file is copyright 2002 Jim Kent, but license is hereby
  * granted for all use - public, private or commercial. */
 
 #include "common.h"
 #include "memgfx.h"
 #include "gemfont.h"
 #include "localmem.h"
 #include "vGfx.h"
 #include "vGfxPrivate.h"
 #include "colHash.h"
 
 static char const rcsid[] = "$Id$";
 
+#ifndef min3
+#define min3(x,y,z) (min(x,min(y,z)))
+/* Return min of x,y, and z. */
+#endif
+
+#ifndef max3
+#define max3(x,y,z) (max(x,max(y,z)))
+/* Return max of x,y, and z. */
+#endif
+
+
 static void mgSetDefaultColorMap(struct memGfx *mg)
 /* Set up default color map for a memGfx. */
 {
 /* Note dependency in order here and in MG_WHITE, MG_BLACK, etc. */
 int i;
 for (i=0; i<ArraySize(mgFixedColors); ++i)
     {
     struct rgbColor *c = &mgFixedColors[i];
     mgAddColor(mg, c->r, c->g, c->b);
     }
 }
 
 
 void mgSetClip(struct memGfx *mg, int x, int y, int width, int height)
 /* Set clipping rectangle. */
 {
 int x2, y2;
 if (x < 0)
     x = 0;
 if (y < 0)
     y = 0;
 x2 = x + width;
 if (x2 > mg->width)
     x2 = mg->width;
 y2 = y + height;
 if (y2 > mg->height)
     y2 = mg->height;
 mg->clipMinX = x;
 mg->clipMaxX = x2;
 mg->clipMinY = y;
 mg->clipMaxY = y2;
 }
 
 void mgUnclip(struct memGfx *mg)
 /* Set clipping rect cover full thing. */
 {
 mgSetClip(mg, 0,0,mg->width, mg->height);
 }
 
 struct memGfx *mgNew(int width, int height)
 /* Return new memGfx. Note new pixel memory is uninitialized */
 {
 struct memGfx *mg;
 
 mg = needMem(sizeof(*mg));
 mg->pixels = needLargeMem(width*height);
 mg->width = width;
 mg->height = height;
 mg->colorHash = colHashNew();
 mgSetDefaultColorMap(mg);
 mgUnclip(mg);
 return mg;
 }
 
 void mgClearPixels(struct memGfx *mg)
 /* Set all pixels to background. */
 {
 zeroBytes(mg->pixels, mg->width*mg->height);
 }
 
 Color mgFindColor(struct memGfx *mg, unsigned char r, unsigned char g, unsigned char b)
 /* Returns closest color in color map to rgb values.  If it doesn't
  * already exist in color map and there's room, it will create
  * exact color in map. */
 {
 struct colHashEl *che;
 if ((che = colHashLookup(mg->colorHash, r, g, b)) != NULL)
     return che->ix;
 if (mgColorsFree(mg))
     return mgAddColor(mg, r, g, b);
 return mgClosestColor(mg, r, g, b);
 }
 
 
 struct rgbColor mgColorIxToRgb(struct memGfx *mg, int colorIx)
 /* Return rgb value at color index. */
 {
 return mg->colorMap[colorIx];
 }
 
 Color mgClosestColor(struct memGfx *mg, unsigned char r, unsigned char g, unsigned char b)
 /* Returns closest color in color map to r,g,b */
 {
 struct rgbColor *c = mg->colorMap;
 int closestDist = 0x7fffffff;
 int closestIx = -1;
 int dist, dif;
 int i;
 for (i=0; i<mg->colorsUsed; ++i)
     {
     dif = c->r - r;
     dist = dif*dif;
     dif = c->g - g;
     dist += dif*dif;
     dif = c->b - b;
     dist += dif*dif;
     if (dist < closestDist)
         {
         closestDist = dist;
         closestIx = i;
         }
     ++c;
     }
 return closestIx;
 }
 
 
 Color mgAddColor(struct memGfx *mg, unsigned char r, unsigned char g, unsigned char b)
 /* Adds color to end of color map if there's room. */
 {
 int colIx = mg->colorsUsed;
 if (colIx < 256)
     {
     struct rgbColor *c = mg->colorMap + mg->colorsUsed;
     c->r = r;
     c->g = g;
     c->b = b;
     mg->colorsUsed += 1;
     colHashAdd(mg->colorHash, r, g, b, colIx);
     }
 return (Color)colIx;
 }
 
 int mgColorsFree(struct memGfx *mg)
 /* Returns # of unused colors in color map. */
 {
 return 256-mg->colorsUsed;
 }
 
 void mgFree(struct memGfx **pmg)
 {
 struct memGfx *mg = *pmg;
 if (mg != NULL)
     {
     if (mg->pixels != NULL)
 	freeMem(mg->pixels);
     colHashFree(&mg->colorHash);
     zeroBytes(mg, sizeof(*mg));
     freeMem(mg);
     }
 *pmg = NULL;
 }
 
 static void nonZeroCopy(Color *d, Color *s, int width)
 /* Copy non-zero colors. */
 {
 Color c;
 int i;
 for (i=0; i<width; ++i)
     {
     if ((c = s[i]) != 0)
         d[i] = c;
     }
 }
 
 static void mgPutSegMaybeZeroClear(struct memGfx *mg, int x, int y, int width, Color *dots, boolean zeroClear)
 /* Put a series of dots starting at x, y and going to right width pixels.
  * Possibly don't put zero dots though. */
 {
 int x2;
 Color *pt;
 if (y < mg->clipMinY || y > mg->clipMaxY)
     return;
 x2 = x + width;
 if (x2 > mg->clipMaxX)
     x2 = mg->clipMaxX;
 if (x < mg->clipMinX)
     {
     dots += mg->clipMinX - x;
     x = mg->clipMinX;
     }
 width = x2 - x;
 if (width > 0)
     {
     pt = _mgPixAdr(mg, x, y);
     if (zeroClear)
         nonZeroCopy(pt, dots, width);
     else
 	memcpy(pt, dots, width);
     }
 }
 
 void mgVerticalSmear(struct memGfx *mg,
 	int xOff, int yOff, int width, int height, 
 	unsigned char *dots, boolean zeroClear)
 /* Put a series of one 'pixel' width vertical lines. */
 {
 while (--height >= 0)
     {
     mgPutSegMaybeZeroClear(mg, xOff, yOff, width, dots, zeroClear);
     ++yOff;
     }
 }
 
 void mgDrawBox(struct memGfx *mg, int x, int y, int width, int height, Color color)
 {
 int i;
 Color *pt;
 int x2 = x + width;
 int y2 = y + height;
 int wrapCount;
 
 if (x < mg->clipMinX)
     x = mg->clipMinX;
 if (y < mg->clipMinY)
     y = mg->clipMinY;
 if (x2 > mg->clipMaxX)
     x2 = mg->clipMaxX;
 if (y2 > mg->clipMaxY)
     y2 = mg->clipMaxY;
 width = x2-x;
 height = y2-y;
 if (width > 0 && height > 0)
     {
     pt = _mgPixAdr(mg,x,y);
     /*colorBin[x][color]++;  increment color count for this pixel */
     wrapCount = _mgBpr(mg) - width;
     while (--height >= 0)
 	{
 	i = width;
 	while (--i >= 0)
 	    *pt++ = color;
 	pt += wrapCount;
 	}
     }
 }
 
 void mgBrezy(struct memGfx *mg, int x1, int y1, int x2, int y2, Color color,
 	int yBase, boolean fillFromBase)
 /* Brezenham line algorithm.  Optionally fill in under line. */
 {
 if (x1 == x2)
     {
     int y,height;
     if (y1 > y2)
 	{
 	y = y2;
 	height = y1-y2+1;
 	}
     else
         {
 	y = y1;
 	height = y2-y1+1;
 	}
     if (fillFromBase)
         {
 	if (y < yBase)
 	    mgDrawBox(mg, x1, y, 1, yBase-y, color);
 	}
     else
         mgDrawBox(mg, x1, y, 1, height, color);
     }
 else if (y1 == y2)
     {
     int x,width;
     if (x1 > x2)
         {
 	x = x2;
 	width = x1-x2+1;
 	}
     else
         {
 	x = x1;
 	width = x2-x1+1;
 	}
     if (fillFromBase)
         {
 	if (y1 < yBase)
 	    mgDrawBox(mg, x, y1, width, yBase - y1, color);
 	}
     else
         {
 	mgDrawBox(mg, x, y1, width, 1, color);
 	}
     }
 else
     {
     int duty_cycle;
     int incy;
     int delta_x, delta_y;
     int dots;
     delta_y = y2-y1;
     delta_x = x2-x1;
     if (delta_y < 0) 
 	{
 	delta_y = -delta_y;
 	incy = -1;
 	}
     else
 	{
 	incy = 1;
 	}
     if (delta_x < 0) 
 	{
 	delta_x = -delta_x;
 	incy = -incy;
 	x1 = x2;
 	y1 = y2;
 	}
     duty_cycle = (delta_x - delta_y)/2;
     if (delta_x >= delta_y)
 	{
 	dots = delta_x+1;
 	while (--dots >= 0)
 	    {
 	    if (fillFromBase)
 		{
 		if (y1 < yBase)
 		    mgDrawBox(mg,x1,y1,1,yBase-y1,color);
 		}
 	    else
 		mgPutDot(mg,x1,y1,color);
 	    duty_cycle -= delta_y;
 	    x1 += 1;
 	    if (duty_cycle < 0)
 		{
 		duty_cycle += delta_x;	  /* update duty cycle */
 		y1+=incy;
 		}
 	    }
 	}
     else
 	{
 	dots = delta_y+1;
 	while (--dots >= 0)
 	    {
 	    if (fillFromBase)
 		{
 		if (y1 < yBase)
 		    mgDrawBox(mg,x1,y1,1,yBase-y1,color);
 		}
 	    else
 		mgPutDot(mg,x1,y1,color);
 	    duty_cycle += delta_x;
 	    y1+=incy;
 	    if (duty_cycle > 0)
 		{
 		duty_cycle -= delta_y;	  /* update duty cycle */
 		x1 += 1;
 		}
 	    }
 	}
     }
 }
 
 void mgDrawLine(struct memGfx *mg, int x1, int y1, int x2, int y2, Color color)
 /* Draw a line from one point to another. */
 {
 mgBrezy(mg, x1, y1, x2, y2, color, 0, FALSE);
 }
 
 void mgFillUnder(struct memGfx *mg, int x1, int y1, int x2, int y2, 
 	int bottom, Color color)
 /* Draw a 4 sided filled figure that has line x1/y1 to x2/y2 at
  * it's top, a horizontal line at bottom as it's bottom, and
  * vertical lines from the bottom to y1 on the left and bottom to
  * y2 on the right. */
 {
 mgBrezy(mg, x1, y1, x2, y2, color, bottom, TRUE);
 }
 
 void mgPutSeg(struct memGfx *mg, int x, int y, int width, Color *dots)
 /* Put a series of dots starting at x, y and going to right width pixels. */
 {
 mgPutSegMaybeZeroClear(mg, x, y, width, dots, FALSE);
 }
 
 void mgPutSegZeroClear(struct memGfx *mg, int x, int y, int width, Color *dots)
 /* Put a series of dots starting at x, y and going to right width pixels.
  * Don't put zero dots though. */
 {
 mgPutSegMaybeZeroClear(mg, x, y, width, dots, TRUE);
 }
 
 
 void mgDrawHorizontalLine(struct memGfx *mg, int y1, Color color)
 /*special case of mgDrawLine, for horizontal line across entire window 
   at y-value y1.*/
 {
 mgDrawLine( mg, mg->clipMinX, y1, mg->clipMaxX, y1, color);
 }
 
 void mgLineH(struct memGfx *mg, int y, int x1, int x2, Color color)
 /* Draw horizizontal line width pixels long starting at x/y in color */
 {
 if (y >= mg->clipMinY && y < mg->clipMaxY)
     {
     int w;
     if (x1 < mg->clipMinX)
         x1 = mg->clipMinX;
     if (x2 > mg->clipMaxX)
         x2 = mg->clipMaxX;
     w = x2 - x1;
     if (w > 0)
         {
 	Color *pt = _mgPixAdr(mg,x1,y);
 	while (--w >= 0)
 	    *pt++ = color;
 	}
     }
 }
 
 
 boolean mgClipForBlit(int *w, int *h, int *sx, int *sy,
 	struct memGfx *dest, int *dx, int *dy)
 {
 /* Make sure we don't overwrite destination. */
 int over;
 
 if ((over = dest->clipMinX - *dx) > 0)
     {
     *w -= over;
     *sx += over;
     *dx = dest->clipMinX;
     }
 if ((over = dest->clipMinY - *dy) > 0)
     {
     *h -= over;
     *sy += over;
     *dy = dest->clipMinY;
     }
 if ((over = *w + *dx - dest->clipMaxX) > 0)
     *w -= over; 
 if ((over = *h + *dy - dest->clipMaxY) > 0)
     *h -= over;
 return (*h > 0 && *w > 0);
 }
 
 void mgTextBlit(int width, int height, int bitX, int bitY,
 	unsigned char *bitData, int bitDataRowBytes, 
 	struct memGfx *dest, int destX, int destY, 
 	Color color, Color backgroundColor)
 {
 UBYTE *inLine;
 UBYTE *outLine;
 UBYTE inLineBit;
 
 if (!mgClipForBlit(&width, &height, &bitX, &bitY, dest, &destX, &destY))
     return;
 
 inLine = bitData + (bitX>>3) + bitY * bitDataRowBytes;
 inLineBit = (0x80 >> (bitX&7));
 outLine = _mgPixAdr(dest,destX,destY);
 while (--height >= 0)
     {
     UBYTE *in = inLine;
     UBYTE *out = outLine;
     UBYTE inBit = inLineBit;
     UBYTE inByte = *in++;
     int i = width;
     while (--i >= 0)
 	{
 	if (inBit & inByte)
 	    *out = color;
 	++out;
 	if ((inBit >>= 1) == 0)
 	    {
 	    inByte = *in++;
 	    inBit = 0x80;
 	    }
 	}
     inLine += bitDataRowBytes;
     outLine += _mgBpr(dest);
     }
 }
 
 void mgTextBlitSolid(int width, int height, int bitX, int bitY,
 	unsigned char *bitData, int bitDataRowBytes, 
 	struct memGfx *dest, int destX, int destY, 
 	Color color, Color backgroundColor)
 {
 UBYTE *inLine;
 UBYTE *outLine;
 UBYTE inLineBit;
 
 if (!mgClipForBlit(&width, &height, &bitX, &bitY, dest, &destX, &destY))
     return;
 inLine = bitData + (bitX>>3) + bitY * bitDataRowBytes;
 inLineBit = (0x80 >> (bitX&7));
 outLine = _mgPixAdr(dest,destX,destY);
 while (--height >= 0)
     {
     UBYTE *in = inLine;
     UBYTE *out = outLine;
     UBYTE inBit = inLineBit;
     UBYTE inByte = *in++;
     int i = width;
     while (--i >= 0)
 	{
 	*out++ = ((inBit & inByte) ? color : backgroundColor);
 	if ((inBit >>= 1) == 0)
 	    {
 	    inByte = *in++;
 	    inBit = 0x80;
 	    }
 	}
     inLine += bitDataRowBytes;
     outLine += _mgBpr(dest);
     }
 }
 
 
 void mgText(struct memGfx *mg, int x, int y, Color color, 
 	MgFont *font, char *text)
 /* Draw a line of text with upper left corner x,y. */
 {
 gfText(mg, font, text, x, y, color, mgTextBlit, MG_WHITE);
 }
 
 void mgTextCentered(struct memGfx *mg, int x, int y, int width, int height, 
 	Color color, MgFont *font, char *text)
 /* Draw a line of text centered in box defined by x/y/width/height */
 {
 int fWidth, fHeight;
 int xoff, yoff;
 fWidth = mgFontStringWidth(font, text);
 fHeight = mgFontPixelHeight(font);
 xoff = x + (width - fWidth)/2;
 yoff = y + (height - fHeight)/2;
 if (font == mgSmallFont())
     {
     xoff += 1;
     yoff += 1;
     }
 mgText(mg, xoff, yoff, color, font, text);
 }
 
 void mgTextRight(struct memGfx *mg, int x, int y, int width, int height, 
 	Color color, MgFont *font, char *text)
 /* Draw a line of text right justified in box defined by x/y/width/height */
 {
 int fWidth, fHeight;
 int xoff, yoff;
 fWidth = mgFontStringWidth(font, text);
 fHeight = mgFontPixelHeight(font);
 xoff = x + width - fWidth - 1;
 yoff = y + (height - fHeight)/2;
 if (font == mgSmallFont())
     {
     xoff += 1;
     yoff += 1;
     }
 mgText(mg, xoff, yoff, color, font, text);
 }
 
 int mgFontPixelHeight(MgFont *font)
 /* How high in pixels is font? */
 {
 return font_cel_height(font);
 }
 
 int mgGetFontPixelHeight(struct memGfx *mg, MgFont *font)
 /* How high in pixels is font? */
 {
 return mgFontPixelHeight(font);
 }
 
 int mgFontLineHeight(MgFont *font)
 /* How many pixels to next line ideally? */
 {
 int celHeight = font_cel_height(font);
 return celHeight + 1 + (celHeight/5);
 }
 
 int mgFontWidth(MgFont *font, char *chars, int charCount)
 /* How wide are a couple of letters? */
 {
 return fnstring_width(font, (unsigned char *)chars, charCount);
 }
 
 int mgFontStringWidth(MgFont *font, char *string)
 /* How wide is a string? */
 {
 return mgFontWidth(font, string, strlen(string));
 }
 
 int mgGetFontStringWidth(struct memGfx *mg, MgFont *font, char *string)
 /* How wide is a string? */
 {
 return mgFontStringWidth(font, string);
 }
 
 int mgFontCharWidth(MgFont *font, char c)
 /* How wide is a character? */
 {
 return mgFontWidth(font, &c, 1);
 }
 
 void mgSlowDot(struct memGfx *mg, int x, int y, int colorIx)
 /* Draw a dot when a macro won't do. */
 {
 mgPutDot(mg, x, y, colorIx);
 }
 
 int mgSlowGetDot(struct memGfx *mg, int x, int y)
 /* Fetch a dot when a macro won't do. */
 {
 return mgGetDot(mg, x, y);
 }
 
 struct memGfx *mgRotate90(struct memGfx *in)
 /* Create a copy of input that is rotated 90 degrees clockwise. */
 {
 int iWidth = in->width, iHeight = in->height;
 struct memGfx *out = mgNew(iHeight, iWidth);
 Color *inCol, *outRow, *outRowStart;
 int i,j;
 
 memcpy(out->colorMap, in->colorMap, sizeof(out->colorMap));
 outRowStart = out->pixels;
 for (i=0; i<iWidth; ++i)
     {
     inCol = in->pixels + i;
     outRow = outRowStart;
     outRowStart += _mgBpr(out);
     j = iHeight;
     while (--j >= 0)
         {
 	outRow[j] = *inCol;
 	inCol += _mgBpr(in);
 	}
     }
 return out;
 }
 
 void mgSetHint(char *hint)
 /* dummy function */
 {
 return;
 }
 
 char *mgGetHint(char *hint)
 /* dummy function */
 {
 return "";
 }
 
 void vgMgMethods(struct vGfx *vg)
 /* Fill in virtual graphics methods for memory based drawing. */
 {
 vg->pixelBased = TRUE;
 vg->close = (vg_close)mgFree;
 vg->dot = (vg_dot)mgSlowDot;
 vg->getDot = (vg_getDot)mgSlowGetDot;
 vg->box = (vg_box)mgDrawBox;
 vg->line = (vg_line)mgDrawLine;
 vg->text = (vg_text)mgText;
 vg->textRight = (vg_textRight)mgTextRight;
 vg->textCentered = (vg_textCentered)mgTextCentered;
 vg->findColorIx = (vg_findColorIx)mgFindColor;
 vg->colorIxToRgb = (vg_colorIxToRgb)mgColorIxToRgb;
 vg->setClip = (vg_setClip)mgSetClip;
 vg->unclip = (vg_unclip)mgUnclip;
 vg->verticalSmear = (vg_verticalSmear)mgVerticalSmear;
 vg->fillUnder = (vg_fillUnder)mgFillUnder;
 vg->drawPoly = (vg_drawPoly)mgDrawPoly;
 vg->setHint = (vg_setHint)mgSetHint;
 vg->getHint = (vg_getHint)mgGetHint;
 vg->getFontPixelHeight = (vg_getFontPixelHeight)mgGetFontPixelHeight;
 vg->getFontStringWidth = (vg_getFontStringWidth)mgGetFontStringWidth;
 }
+
+
+struct hslColor mgRgbToHsl(struct rgbColor rgb)
+/* Convert RGB to HSL colorspace (see http://en.wikipedia.org/wiki/HSL_and_HSV)
+ * In HSL, Hue is the color in the range [0,360) with 0=red 120=green 240=blue,
+ * Saturation goes from a shade of grey (0) to fully saturated color (1000), and
+ * Lightness goes from black (0) through the hue (500) to white (1000). */
+{
+unsigned char rgbMax = max3(rgb.r, rgb.g, rgb.b);
+unsigned char rgbMin = min3(rgb.r, rgb.g, rgb.b);
+unsigned char delta = rgbMax - rgbMin;
+unsigned short minMax = rgbMax + rgbMin;
+int divisor;
+struct hslColor hsl = { 0.0, 0, (1000*minMax+255)/(2*255) }; // round up
+
+// if max=min then no saturation, and this is gray
+if (rgbMax == rgbMin)
+    return hsl;
+else if (hsl.l <= 500)
+    divisor = minMax;
+else
+    divisor = (2*255-minMax);
+hsl.s = (1000*delta + divisor/2)/divisor; // round up
+
+// Saturation so compute hue 0..360 degrees (same as for HSV)
+if (rgbMax == rgb.r) // red is 0 +/- offset in blue or green direction
+    {
+    hsl.h = 0 + 60.0*(rgb.g - rgb.b)/delta;
+    }
+else if (rgbMax == rgb.g) // green is 120 +/- offset in blue or red direction
+    {
+    hsl.h = 120 + 60.0*(rgb.b - rgb.r)/delta;
+    }
+else // rgb_max == rgb.b // blue is 240 +/- offset in red or green direction
+    {
+    hsl.h = 240 + 60.0*(rgb.r - rgb.g)/delta;
+    }
+// normalize to [0,360)
+if (hsl.h < 0.0)
+    hsl.h += 360.0;
+else if (hsl.h >= 360.0)
+    hsl.h -= 360.0;
+return hsl;
+}
+
+
+struct hsvColor mgRgbToHsv(struct rgbColor rgb) 
+/* Convert RGB to HSV colorspace (see http://en.wikipedia.org/wiki/HSL_and_HSV)
+ * In HSV, Hue is the color in the range [0,360) with 0=red 120=green 240=blue,
+ * Saturation goes from white (0) to fully saturated color (1000), and
+ * Value goes from black (0) through to the hue (1000). */
+{
+unsigned char rgbMax = max3(rgb.r, rgb.g, rgb.b);
+unsigned char rgbMin = min3(rgb.r, rgb.g, rgb.b);
+unsigned char delta = rgbMax - rgbMin;
+struct hsvColor hsv = {0.0, 0, 1000*rgbMax/255};
+
+if (hsv.v == 0) 
+    return hsv;
+hsv.s = 1000*delta/rgbMax;
+// if no saturation, then this is gray
+if (hsv.s == 0) 
+    return hsv;
+// Saturation so compute hue 0..360 degrees (same as for HSL)
+if (rgbMax == rgb.r) // red is 0 +/- offset in blue or green direction
+    {
+    hsv.h = 0 + 60.0*(rgb.g - rgb.b)/delta;
+    } 
+else if (rgbMax == rgb.g) // green is 120 +/- offset in blue or red direction
+    {
+    hsv.h = 120 + 60.0*(rgb.b - rgb.r)/delta;
+    } 
+else // rgb_max == rgb.b // blue is 240 +/- offset in red or green direction
+    {
+    hsv.h = 240 + 60.0*(rgb.r - rgb.g)/delta;
+    }
+// normalize to [0,360)
+if (hsv.h < 0.0)
+    hsv.h += 360.0;
+else if (hsv.h >= 360.0)
+    hsv.h -= 360.0;
+return hsv;
+}
+
+
+struct rgbColor mgHslToRgb(struct hslColor hsl)
+/* Convert HSL to RGB colorspace http://en.wikipedia.org/wiki/HSL_and_HSV */
+{
+int p, q;
+double r, g, b;
+//unsigned short p, q, r, g, b;
+double tR, tG, tB;
+
+if( hsl.s == 0 ) // achromatic (grey)
+    return (struct rgbColor) {(255*hsl.l+500)/1000, (255*hsl.l+500)/1000, (255*hsl.l+500)/1000};
+if (hsl.l <= 500)
+    q = hsl.l + (hsl.l*hsl.s+500)/1000;
+else
+    q = hsl.l + hsl.s - (hsl.l*hsl.s+500)/1000;
+p = 2 * hsl.l - q;
+hsl.h /= 360.0; // normalize h to 0..1
+tR = hsl.h + 1/3.0;
+tG = hsl.h;
+tB = hsl.h - 1/3.0;
+if (tR < 0.0)
+    tR += 1.0;
+else if (tR > 1.0)
+    tR -= 1.0;
+if (tG < 0.0)
+    tG += 1.0;
+else if (tG > 1.0)
+    tG -= 1.0;
+if (tB < 0.0)
+    tB += 1.0;
+else if (tB > 1.0)
+    tB -= 1.0;
+// Red
+if (tR < 1/6.0)
+    r = p + (q-p)*6*tR;
+else if (tR < 0.5)
+    r = q;
+else if (tR < 2/3.0)
+    r = p + (q-p)*6*(2/3.0 - tR);
+else
+    r = p;
+// Green
+if (tG < 1/6.0)
+    g = p + (q-p)*6*tG;
+else if (tG < 0.5)
+    g = q;
+else if (tG < 2/3.0)
+    g = p + (q-p)*6*(2/3.0 - tG);
+else
+    g = p;
+// Blue
+if (tB < 1/6.0)
+    b = p + (q-p)*6*tB;
+else if (tB < 0.5)
+    b = q;
+else if (tB < 2/3.0)
+    b = p + (q-p)*6*(2/3.0 - tB);
+else
+    b = p;
+return (struct rgbColor) {(255*r+500)/1000, (255*g+500)/1000, (255*b+500)/1000}; // round up
+}
+
+
+struct rgbColor mgHsvToRgb(struct hsvColor hsv)
+/* Convert HSV to RGB colorspace http://en.wikipedia.org/wiki/HSL_and_HSV */
+{
+int i;
+double f;
+unsigned short low, q, t, r, g, b;
+
+if( hsv.s == 0 ) // achromatic (grey)
+    return (struct rgbColor) {(255*hsv.v+500)/1000, (255*hsv.v+500)/1000, (255*hsv.v+500)/1000};
+hsv.h /= 60.0; 
+i = (int)floor( hsv.h );             // sector 0 to 5
+f = hsv.h - i;                       // fractional part (distance) from hue 
+// hsv.v is highest r,g, or b value
+// low value is related to saturation
+low = hsv.v - (hsv.v * hsv.s / 1000); // lowest r,g, or b value
+t = low + (hsv.v - low) * f;         // scaled value from low..high
+q = low + (hsv.v - low) * (1.0-f);   // scaled value from low..high
+
+switch( i )
+    {
+    case 0:
+	r = hsv.v;
+        g = t;
+        b = low;
+        break;
+    case 1:
+        r = q;
+        g = hsv.v;
+        b = low;
+        break;
+    case 2:
+        r = low;
+        g = hsv.v;
+        b = t;
+        break;
+    case 3:
+        r = low;
+        g = q;
+        b = hsv.v;
+        break;
+    case 4:
+        r = t;
+        g = low;
+        b = hsv.v;
+        break;
+    default:                // case 5:
+        r = hsv.v;
+        g = low;
+        b = q;
+        break;
+    }
+return (struct rgbColor) {(255*r+500)/1000, (255*g+500)/1000, (255*b+500)/1000};
+}
+
+
+struct rgbColor mgRgbTransformHsl(struct rgbColor in, double h, double s, double l)
+/* Transform rgb 'in' value using
+ *   hue shift 'h' (0..360 degrees), 
+ *   saturation scale 's', and 
+ *   lightness scale 'l'
+ * Returns the transformed rgb value 
+ * Use H=0, S=L=1 for identity transformation
+ */
+{
+struct hslColor hsl = mgRgbToHsl(in);
+hsl.h += h;
+while (hsl.h < 0.0)
+    hsl.h += 360.0;
+while (hsl.h > 360.0)
+    hsl.h -= 360.0;
+hsl.s = min(max(hsl.s * s, 0), 1000);
+hsl.l = min(max(hsl.l * l, 0), 1000);
+return mgHslToRgb(hsl);
+}
+
+
+struct rgbColor mgRgbTransformHsv(struct rgbColor in, double h, double s, double v)
+/* Transform rgb 'in' value using
+ *   hue shift 'h' (0..360 degrees), 
+ *   saturation scale 's', and 
+ *   value scale 'v'
+ * Returns the transformed rgb value 
+ * Use H=0, S=V=1 for identity transformation
+ */
+{
+struct hsvColor hsv = mgRgbToHsv(in);
+hsv.h += h;
+while (hsv.h < 0.0)
+    hsv.h += 360.0;
+while (hsv.h > 360.0)
+    hsv.h -= 360.0;
+hsv.s = min(max(hsv.s * s, 0), 1000);
+hsv.v = min(max(hsv.v * v, 0), 1000);
+return mgHsvToRgb(hsv);
+}
+