I am trying to convert a function to create an HSV circle from Delphi to C #, but the result does not match the correct path.
My goal is to make an application for Windows Phone 7, and I use only the WP7.1 SDK, plus WriteableBitmapEx
.
Delphi Code:
FUNCTION CreateHueSaturationCircle(CONST size: INTEGER; CONST ValueLevel: INTEGER; CONST BackgroundColor: TColor): TBitmap; VAR dSquared: INTEGER; H,S,V: INTEGER; i: INTEGER; j: INTEGER; Radius: INTEGER; RadiusSquared: INTEGER; row: pRGBTripleArray; X: INTEGER; Y: INTEGER; BEGIN RESULT := TBitmap.Create; RESULT.PixelFormat := pf24bit; RESULT.Width := size; RESULT.Height := size; // Fill with background color RESULT.Canvas.Brush.Color := BackGroundColor; RESULT.Canvas.FillRect(RESULT.Canvas.ClipRect); Radius := size DIV 2; RadiusSquared := Radius * Radius; V := ValueLevel; FOR j := 0 TO RESULT.Height - 1 DO BEGIN Y := Size - 1 - j - Radius; {Center is Radius offset} row := RESULT.Scanline[Size - 1 - j]; FOR i := 0 TO RESULT.Width - 1 DO BEGIN X := i - Radius; dSquared := X * X + Y * Y; IF dSquared <= RadiusSquared THEN BEGIN S := ROUND((255 * SQRT(dSquared)) / Radius); H := ROUND(180 * (1 + ArcTan2(X, Y) / PI)); // 0..360 degrees // Shift 90 degrees so H=0 (red) occurs along "X" axis H := H + 90; IF H > 360 THEN H := H - 360; row[i] := HSVtoRGBTriple(H,S,V) END END; END; END; FUNCTION HSVtoRGBTriple(CONST H,S,V: INTEGER): TRGBTriple; CONST divisor: INTEGER = 255 * 60; VAR f: INTEGER; hTemp: INTEGER; p,q,t: INTEGER; VS: INTEGER; BEGIN IF S = 0 THEN RESULT := RGBtoRGBTriple(V, V, V) // achromatic: shades of gray ELSE BEGIN // chromatic color IF H = 360 THEN hTemp := 0 ELSE hTemp := H; f := hTemp MOD 60; // f is IN [0, 59] hTemp := hTemp DIV 60; // h is now IN [0,6) VS := V * S; p := V - VS DIV 255; // p = v * (1 - s) q := V - (VS*f) DIV divisor; // q = v * (1 - s*f) t := V - (VS*(60 - f)) DIV divisor; // t = v * (1 - s * (1 - f)) CASE hTemp OF 0: RESULT := RGBtoRGBTriple(V, t, p); 1: RESULT := RGBtoRGBTriple(q, V, p); 2: RESULT := RGBtoRGBTriple(p, V, t); 3: RESULT := RGBtoRGBTriple(p, q, V); 4: RESULT := RGBtoRGBTriple(t, p, V); 5: RESULT := RGBtoRGBTriple(V, p, q); ELSE RESULT := RGBtoRGBTriple(0,0,0) // should never happen; // avoid compiler warning END END END
Delphi code results:
My C # code:
public struct HSV { public float h; public float s; public float v; } public void createHsvCircle() { int size = 300; wb = new WriteableBitmap(size, size); wb.Clear(GraphicsUtils.WhiteColor); int radius = size / 2; int radiusSquared = radius * radius; int x; int y; int dSquared; HSV hsv; hsv.v = 255F; for (int j = 0; j < size; j++) { y = size - 1 - j - radius; for (int i = 0; i < size; i++) { x = i - radius; dSquared = x * x + y * y; if (dSquared <= radiusSquared) { hsv.s = (float) Math.Round((255 * Math.Sqrt(dSquared)) / radius); hsv.h = (float) Math.Round(180 * (1 + Math.Atan2(y, x) / Math.PI)); hsv.h += 90; if (hsv.h > 360) { hsv.h -= 360; } Color color = GraphicsUtils.HsvToRgb(hsv); wb.SetPixel(i, j, color); } } } wb.Invalidate(); } public static Color HsvToRgb(float h, float s, float v) { h = h / 360; if (s > 0) { if (h >= 1) h = 0; h = 6 * h; int hueFloor = (int)Math.Floor(h); byte a = (byte)Math.Round(RGB_MAX * v * (1.0 - s)); byte b = (byte)Math.Round(RGB_MAX * v * (1.0 - (s * (h - hueFloor)))); byte c = (byte)Math.Round(RGB_MAX * v * (1.0 - (s * (1.0 - (h - hueFloor))))); byte d = (byte)Math.Round(RGB_MAX * v); switch (hueFloor) { case 0: return Color.FromArgb(RGB_MAX, d, c, a); case 1: return Color.FromArgb(RGB_MAX, b, d, a); case 2: return Color.FromArgb(RGB_MAX, a, d, c); case 3: return Color.FromArgb(RGB_MAX, a, b, d); case 4: return Color.FromArgb(RGB_MAX, c, a, d); case 5: return Color.FromArgb(RGB_MAX, d, a, b); default: return Color.FromArgb(RGB_MAX, 0, 0, 0); } } else { byte d = (byte)(v * RGB_MAX); return Color.FromArgb(255, d, d, d); } } public static Color HsvToRgb(HSV hsv) { return HsvToRgb(hsv.h, hsv.s, hsv.v); }
My C # result:
What am I doing wrong?
Thanks in advance.
EDIT DECISION
With a great answer from @Aybe, I could make a working version from HSV whell.
This is the working code for the WP7 SDK:
public const double PI = 3.14159265358979323846264338327950288d; public void createHsvCircle(double value = 1.0d) { if (value < 0.0d || value > 1.0d) throw new ArgumentOutOfRangeException("value"); var size = 1024; wb = new WriteableBitmap(size, size); // fill with white. var white = Colors.White; for (int index = 0; index < wb.Pixels.Length; index++) { wb.Pixels[index] = 0xFF << 24 | white.R << 16 | white.G << 8 | white.B; } var cx = size / 2; var cy = size / 2; var radius = cx; var radiusSquared = radius * radius; for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { var x = i - cx; var y = j - cy; var distance = (double)x * x + y * y; if (distance <= radiusSquared) // In circle { var angle = 180.0d * (1 + Math.Atan2(x, y) / PI); // shift 90 degrees so H=0 (red) occurs along "X" axis angle += 90.0d; if (angle > 360.0d) { angle -= 360.0d; } var hue = angle / 360.0d; // hue must be into 0 to 1. var saturation = Math.Sqrt(distance) / radius; // saturation must be into 0 to 1. var hsv = new HSV(hue, saturation, value); var rgb = RGB.FromHsv(hsv.H, hsv.S, hsv.V); wb.Pixels[j * size + i] = 0xFF << 24 | rgb.R << 16 | rgb.G << 8 | rgb.B; } } } wb.Invalidate(); } public static RGB FromHsv(double hue, double saturation, double value) { if (hue < 0.0d || hue > 1.0d) throw new ArgumentOutOfRangeException("hue"); if (saturation < 0.0d || saturation > 1.0d) throw new ArgumentOutOfRangeException("saturation"); if (value < 0.0d || value > 1.0d) throw new ArgumentOutOfRangeException("value"); if (saturation == 0.0d) { var b1 = (byte)(value * 255); return new RGB(b1, b1, b1); } double r; double g; double b; var h = hue * 6.0d; if (h == 6.0d) { h = 0.0d; } int i = (int)Math.Floor(h); var v1 = value * (1.0d - saturation); var v2 = value * (1.0d - saturation * (h - i)); var v3 = value * (1.0d - saturation * (1.0d - (h - i))); switch (i) { case 0: r = value; g = v3; b = v1; break; case 1: r = v2; g = value; b = v1; break; case 2: r = v1; g = value; b = v3; break; case 3: r = v1; g = v2; b = value; break; case 4: r = v3; g = v1; b = value; break; default: r = value; g = v1; b = v2; break; } r = r * 255.0d; if (r > 255.0d) { r = 255.0d; } g = g * 255.0d; if (g > 255.0d) { g = 255.0d; } b = b * 255.0d; if (b > 255.0d) { b = 255.0d; } return new RGB((byte)r, (byte)g, (byte)b); }
And now a new result:
Thanks!