I'm trying to convert an function to create a HSV Circle from Delphi to C#, but the result is not being in a correct way.
My target is to do an app for Windows Phone 7, and I'm only using the WP7.1 SDK, plus WriteableBitmapEx
library.
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
The results from Delphi code:
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 I'm doing it wrong?
Thanks in advance.
EDITED WITH SOLUTION
With the great answer from @Aybe, I could do a working version from HSV whell.
This is the working code for 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, the new result:
Thanks!
After spending an hour or so, I learned a few things in the process ...
Now the code : (works for any size)
This is HSL but I gave you the url where you have other algos.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace ColorWheel
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void BuildWheel()
{
var width = 1024;
var height = width;
var cx = width/2;
var cy = height/2;
var colors = new int[width*height];
var gray = Colors.Gray.ToBgr32();
for (int index = 0; index < colors.Length; index++) colors[index] = gray;
var radius = cx;
var radiusSquared = radius*radius;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
var x = j - cx;
var y = i - cy;
var distanceSquared = (double) x*x + y*y;
if (distanceSquared <= radiusSquared) // In circle
{
var h = Math.Atan2(x, y).ToDegrees() + 180.0d; // Angle
var s = 1.0d;
var l = (1.0d - ((1.0d/radiusSquared)*distanceSquared)); // 1 - (distance normalized)
var hsl = new HSL((float) h, (float) s, (float) l);
var rgb = RGB.FromHsl(hsl.H, hsl.S, hsl.L);
colors[i*width + j] = rgb.R << 16 | rgb.G << 8 | rgb.B;
}
}
}
var bitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgr32, null);
bitmap.WritePixels(new Int32Rect(0, 0, width, height), colors, width*4, 0);
image.Source = bitmap;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
BuildWheel();
}
}
public static class Helpers
{
public static double ToDegrees(this double radians)
{
return radians*57.2957795130823; // radians * (180.0d / Math.PI)
}
public static double ToRadians(this double degrees)
{
return degrees*0.0174532925199433; // degrees * (Math.PI / 180.0d)
}
}
public static class ColorExtensions
{
public static Color FromBgr32(this Int32 color)
{
return Color.FromRgb((byte) ((color & 0xFF0000) >> 16), (byte) ((color & 0xFF00) >> 8), (byte) (color & 0xFF));
}
public static int ToBgr32(this Color color)
{
return color.R << 16 | color.G << 8 | color.B;
}
}
/// <summary>
/// Represents a color in an HSL space.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct HSL
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly double _h;
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly double _s;
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly double _l;
/// <summary>
/// Returns the fully qualified type name of this instance.
/// </summary>
/// <returns> A <see cref="T:System.String" /> containing a fully qualified type name. </returns>
/// <filterpriority>2</filterpriority>
public override string ToString()
{
return string.Format("H: {0}, S: {1}, L: {2}", _h, _s, _l);
}
/// <summary>
/// Create a new instance of <see cref="HSL" /> .
/// </summary>
/// <param name="h"> Value of <see cref="H" /> component. </param>
/// <param name="s"> Value of <see cref="S" /> component. </param>
/// <param name="l"> Value of <see cref="L" /> component. </param>
public HSL(double h, double s, double l)
{
_h = h;
_s = s;
_l = l;
}
/// <summary>
/// Gets the value of the hue component.
/// </summary>
public double H
{
get { return _h; }
}
/// <summary>
/// Gets the value of the saturation component.
/// </summary>
public double S
{
get { return _s; }
}
/// <summary>
/// Gets the value of the lightness component.
/// </summary>
public double L
{
get { return _l; }
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (obj.GetType() != typeof (HSL)) return false;
return Equals((HSL) obj);
}
public bool Equals(HSL other)
{
return other._h.Equals(_h) && other._l.Equals(_l) &&
other._s.Equals(_s);
}
/// <summary>
/// Create a new instance of <see cref="HSL" /> , from RGB values.
/// </summary>
/// <param name="red"> Value of the red component. </param>
/// <param name="green"> Value of the green component. </param>
/// <param name="blue"> Value of the blue component. </param>
/// <returns> <see cref="HSL" /> instance created. </returns>
public static HSL FromRGB(byte red, byte green, byte blue)
{
var r1 = red/255.0d;
var g1 = green/255.0d;
var b1 = blue/255.0d;
var min = Math.Min(r1, Math.Min(g1, b1));
var max = Math.Max(r1, Math.Max(g1, b1));
var l = (max + min)/2.0d;
var s = 0.0d;
var h = 0.0d;
if (min == max)
{
h = 0.0d;
s = 0.0d;
}
else
{
if (l < 0.5d)
{
s = (max - min)/(max + min);
}
else if (l >= 0.5d)
{
s = (max - min)/(2.0d - max - min);
}
if (r1 == max)
{
h = (g1 - b1)/(max - min);
}
else if (g1 == max)
{
h = 2.0d + (b1 - r1)/(max - min);
}
else if (b1 == max)
{
h = 4.0d + (r1 - g1)/(max - min);
}
}
h *= 60.0d;
if (h < 0.0d)
h += 360.0d;
return new HSL(h, s, l);
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns> A 32-bit signed integer that is the hash code for this instance. </returns>
/// <filterpriority>2</filterpriority>
public override int GetHashCode()
{
unchecked
{
var result = _h.GetHashCode();
result = (result*397) ^ _l.GetHashCode();
result = (result*397) ^ _s.GetHashCode();
return result;
}
}
public static BitmapSource GetHslPalette(int width = 360, int height = 100)
{
// Creates an HSL palette image like in Photoshop, etc ...
var pixels = new int[width*height];
const double saturation = 1.0d;
for (var y = 0; y < height; y++)
{
for (var x = 0; x < width; x++)
{
var hue = (1.0d/width)*x*360.0d;
var lightness = 1.0d - ((1.0f/height)*y);
var rgb = RGB.FromHsl(hue, saturation, lightness);
pixels[y*width + x] = 0xFF << 24 | rgb.R << 16 | rgb.G << 8 | rgb.B;
}
}
return BitmapSource.Create(width, height, 96, 96, PixelFormats.Pbgra32, null, pixels, width*4);
}
public static bool operator ==(HSL left, HSL right)
{
return left.Equals(right);
}
public static bool operator !=(HSL left, HSL right)
{
return !left.Equals(right);
}
}
/// <summary>
/// Represents a color in an RGB space.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct RGB
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly byte _r;
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly byte _g;
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly byte _b;
/// <summary>
/// Create a new instance of <see cref="RGB" /> .
/// </summary>
/// <param name="r"> Value of red component. </param>
/// <param name="g"> Value of green component. </param>
/// <param name="b"> Value of blue component. </param>
public RGB(byte r, byte g, byte b)
{
_r = r;
_g = g;
_b = b;
}
/// <summary>
/// Returns the fully qualified type name of this instance.
/// </summary>
/// <returns> A <see cref="T:System.String" /> containing a fully qualified type name. </returns>
/// <filterpriority>2</filterpriority>
public override string ToString()
{
return string.Format("R: {0}, G: {1}, B: {2}", _r, _g, _b);
}
/// <summary>
/// Gets the value of the red component.
/// </summary>
public byte R
{
get { return _r; }
}
/// <summary>
/// Gets the value of the green component.
/// </summary>
public byte G
{
get { return _g; }
}
/// <summary>
/// Gets the value of the blue component.
/// </summary>
public byte B
{
get { return _b; }
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (obj.GetType() != typeof (RGB)) return false;
return Equals((RGB) obj);
}
public bool Equals(RGB other)
{
return other._b == _b && other._g == _g && other._r == _r;
}
/// <summary>
/// Create a new instance of <see cref="RGB" /> , from HSL values.
/// </summary>
/// <param name="hue"> Hue, from 0.0 to 360.0. </param>
/// <param name="saturation"> Saturation, from 0.0 to 1.0. </param>
/// <param name="lightness"> Lightness, from 0.0 to 1.0. </param>
/// <returns> <see cref="RGB" /> instance created. </returns>
public static RGB FromHsl(double hue, double saturation, double lightness)
{
if (hue < 0.0d || hue > 360.0d) throw new ArgumentOutOfRangeException("hue");
if (saturation < 0.0d || saturation > 1.0d) throw new ArgumentOutOfRangeException("saturation");
if (lightness < 0.0d || lightness > 1.0d) throw new ArgumentOutOfRangeException("lightness");
if (saturation == 0.0d)
{
var b1 = (byte) (lightness*255);
return new RGB(b1, b1, b1);
}
var t2 = 0.0d;
if (lightness < 0.5d)
t2 = lightness*(1.0d + saturation);
else if (lightness >= 0.5d)
t2 = lightness + saturation - lightness*saturation;
var t1 = 2.0d*lightness - t2;
var h = hue/360.0d;
var tr = h + 1.0d/3.0d;
var tg = h;
var tb = h - 1.0d/3.0d;
tr = tr < 0.0d ? tr + 1.0d : tr > 1.0d ? tr - 1.0d : tr;
tg = tg < 0.0d ? tg + 1.0d : tg > 1.0d ? tg - 1.0d : tg;
tb = tb < 0.0d ? tb + 1.0d : tb > 1.0d ? tb - 1.0d : tb;
double r;
if (6.0d*tr < 1.0d)
r = t1 + (t2 - t1)*6.0d*tr;
else if (2.0d*tr < 1.0d)
r = t2;
else if (3.0d*tr < 2.0d)
r = t1 + (t2 - t1)*((2.0d/3.0d) - tr)*6.0d;
else
r = t1;
double g;
if (6.0d*tg < 1.0d)
g = t1 + (t2 - t1)*6.0d*tg;
else if (2.0d*tg < 1.0d)
g = t2;
else if (3.0d*tg < 2.0d)
g = t1 + (t2 - t1)*((2.0d/3.0d) - tg)*6.0d;
else
g = t1;
double b;
if (6.0d*tb < 1.0d)
b = t1 + (t2 - t1)*6.0d*tb;
else if (2.0d*tb < 1.0d)
b = t2;
else if (3.0d*tb < 2.0d)
b = t1 + (t2 - t1)*((2.0d/3.0d) - tb)*6.0d;
else
b = t1;
return new RGB((byte) (r*255), (byte) (g*255), (byte) (b*255));
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns> A 32-bit signed integer that is the hash code for this instance. </returns>
/// <filterpriority>2</filterpriority>
public override int GetHashCode()
{
unchecked
{
var result = _b.GetHashCode();
result = (result*397) ^ _g.GetHashCode();
result = (result*397) ^ _r.GetHashCode();
return result;
}
}
public static bool operator ==(RGB left, RGB right)
{
return left.Equals(right);
}
public static bool operator !=(RGB left, RGB right)
{
return !left.Equals(right);
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With