Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting HSV circle code from Delphi to C#

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:

delphi img

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:

csharp

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:

okimg

Thanks!

like image 200
Rafael Cruz Avatar asked Apr 23 '12 22:04

Rafael Cruz


1 Answers

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);
        }
    }
}
like image 74
aybe Avatar answered Sep 28 '22 21:09

aybe