App Marketing: Perspective Marketing Image Template

App Marketing: Perspective Marketing Image Template

[This post got a mention on Microsoft Channel 9 This Week]

Nokia Lumia 1520 is a gorgeous phone. So is your Windows Phone app’s hub in panorama. And you do know that a well developed app is just the half way. Getting the app to its audience is the second half. I created this layered GIMP image to easily sandwich a panorama hub between the layers and showcase your app with your own shining colors. You need to do following to create your app’s panorama with this template:

  • Have all your panorama pages in a single image. 4 or 5 pages will work great.
  • Copy aforesaid panorama image in this XCF.
  • Adjust perspective, add your colors, and export!

With very little efforts you can have your app in your choice of colors.

Combined

Following is the detailed step-by-step of the process with example of Windows Phone Channel 9 app:

First, grab the GIMP XCF (PSD here). If you do not have GIMP, download here, it’s open-source and free. Prepare an image of all the panorama items of your app (a little help here).

Layers!

01
Open PanoramaAppShowcaseOn1520.xcf in GIMP. Look at the Layers windows. There are multiple groups and layers inside. Some layers/groups are not editable. Their names start with [DoNotEdit]. So, do not edit them. The layers you will be editing are – “YourApp.YourAppGoesHere”, “UseBucketToolAndFillAnyColorHere”, and “UseBucketToolAndFillBackgroundColor”.

Bring Your App In

02

  1. Open “YourApp” layer group and select “YourAppGoesHere” layer.
  2. Copy or Open As Layer from File menu, your app’s panorama image. If you Open As Layer, drag it down to just above YourAppGoesHere and right click and merge down. If you copy, as you see in image, a floating layer will be created.
  3. Click on Anchor the floating button at the bottom. You will see now your panorama image is in the layer YourAppGoesHere

Prepare to Skew

03

  1. Make sure YourAppGoesHere layer is selected.
  2. Slide opacity to about 50.
  3. Select Layer > “Autocrop Layer” menu to crop this layer. You will see yellow marching ant line around the layer
  4. Choose perspective tool.

Go 3D

04
The layer now has a grid with 4 handles on corners.

  1. Grab a corner and drag it as close as possible to the corresponding corner on white base. Don’t worry about matching corners exactly, just keep close. Repeat the process for all corners. Look at black arrows in the image.

Now your image’s layout looks something like this:
04a

Micro Adjust and Transform

05
You are still in Perspective transform edit mode. Here you will zoom-in to every corner and match every corner exactly with the corner of the white base.

  1. To zoom-in to the corner you want to adjust, take your mouse pointer on the corner, and ctrl+mousewheelup to zoom-in max. Match both the corners pixel perfect. Repeat for all the four corners. Zoom-in and out to make sure all the corners are matching.
  2. Click on Transform button.
  3. Your image now looks something like this:
    05a

Your Colors

06

    These steps are to easily change phone’s color.

  1. Select UseBucketToolAndFillAnyColorHere layer.
  2. Click on foreground color and pick a color from color picker.
  3. Select Bucket tool from Tools menu
  4. Click anywhere inside the image

More Color and Done

07

  1. If you want to edit background color select UseBucketFillBackgroundColor.
  2. Click on foreground color and pick a color from color picker.
  3. Click anywhere on image
  4. Open File menu and select Export As… Type “filename.png” in the File Name field and save.

If everything goes well, you will have a gorgeous image like this for your app:
08

And this is my app in the same template:
Avirall | Windows Phone | Timer | Stopwatch | Sports | Nokia | Nokia Lumia | Windows Phone 8.1 | Panorama

Accelerate App Reviews

Accelerate App Reviews

Asking for review in your app? It’s tricky. If not done with due diligence, on encountering your request to review, users might get annoyed and still worse, they could rate your app low even though they liked it. A simplistic solution of asking for review after N days from installation (or first use) and N days after user has chosen “remind me later”, is not an appropriate solution. Why? User might have installed your app, ran it, exited, came back after N days and they were presented with a review request message, whereas, they had only used the app once. This is not a nice experience. Asking for review when user is exiting the app is less effective because user already has something else in their mind when they are coming out of the app.

Though there is no one solution that fits all, but this is how I try to tackle this scenario in my app. It’s not the “number of days” but “total minutes user has used your app” is my criteria to decide the interval of popping review message. I keep a record of overall usage of the app, and after a designated number of minutes, I pop a request to user. You might have to go through some trial and error to come up with the right “number of minutes” when you want to request. Determine whether your app is highly immersive or a quick open and shut type of app. In an immersive app|game you might want your review requests at longer intervals. In a less immersive app, you would rather ask earlier and at a less frequency. Then, you might want to gradually decrease the interval between requests during session. Don’t forget to code your logic in such a way that you could easily tune request intervals and accumulative usage time and update your app as soon as you realize that a change in the times is required. Here is pseudo-code for indication purpose:


[THIS IS PSEUDO CODE FOR INDICATION ONLY]

constant int POP_REQUEST_AFTER_ACCUMULATIVE_USAGE (adjusting knob) 
constant int POP_REQUEST_INTERVAL (adjusting knob : Initial interval between requests in the session) 
constant int POP_REQUEST_INTERVAL_DECREASE_BY (adjusting knob : Decrease interval after every request) 
constant int POP_REQUEST_INTERVAL_MINIMUM (adjusting knob : interval should not go below this)

int AccumulativeAppUsage (persist this info in storage)
DateTime LastReviewRequest;
Int ReviewRequestInterval = POP_REQUEST_INTERVAL;

App.Start | App.Activate
	DateTime AppStart
	LastReviewRequest = DateTime.Now //reset

App.Stop | App.Deactivate
	AccumulativeAppUsage += DateTime.NOW – AppStart
	Save AccumulativeAppUsage

Page.[identify the event which triggers PopRequest method]
	PopRequest()

PopRequest()
	totalUsageTillNow = AccumulativeAppUsage += DateTime.NOW – AppStart
	if(totalUsageTillNow >= POP_REQUEST_AFTER_ACCUMULATIVE_USAGE)
	{
		If(DateTime.Now – LastReviewRequest >= ReviewRequestInterval)
		{
			Show review request
			LastReviewRequest = DateTime.Now
			If(ReviewRequestInterval > POP_REQUEST_INTERVAL_MINIMUM)
				ReviewRequestInterval -= POP_REQUEST_INTERVAL_DECREASE_BY
		}
	}

App Marketing: Panorama Image Template

App Marketing: Panorama Image Template

(An improved one with perspective)

If your app has a panorama hub, an image showing all the pano items with device could be a very impressive marketing image. I have created this template which makes your life a bit easier, if you want to create such image. You can download the template XCF image file here (yes this is a GIMP template 0_0. I may create PSD edition if you insist). Currently the device image in this template is Microsoft Windows Phone stock emulator image, Nokia Lumia device images could also come in future. When you open this XCF in GIMP you get something like this:

MarketingCompInGiMP

Take snapshots of your app’s pano items from emulator and replace screen 1, 2, 3, and 4 in GIMP with respective images. Once you are satisfied with the image, export it to JPG/PNG with File > Export option. You might want to scale the image down before you send it out. Open the exported JPG in GIMP and use Image > Scale option to scale the image to desired scale.

Notes:

  • This is 1080P edition (480X800 version is on its way, see below), so your emulator/device screen shots should also be 1080P.
  • If your pano has BG image, disable it before you take screen shots and give it a plain #00ff00 color. After copying the screens in GIMP, select #00ff00 color in layer and replace it with alpha. Repeat for all pano items. In the end have a stitched, single background image, and copy it on the background layer in GIMP. You are doomed if you have #00ff00 color in other parts of your pano images, other than BG :-D. (Just kidding, choose some other color for BG which is unique).
  • Because of parallax effect in panorama, you will get repetitive panorama title in your app’s screen shots. Initially, copy the screen as they are. Then, in GIMP, with Rectangle selection tool (R), select the title part, Ctrl+X and Ctrl+V to make another layer. Do same with all the pano items. Select Move tool (M) and move pano item 2, 3, 4’s title sections to left to adjust with pano item 1.
  • Watch this space for a 480X800 version. Or follow me on Twitter @sanjayAtPilcrow.

C#|.NET : Size String, Truncate Lines

Swiss Crop Circle 2009 Aerial by Kecko, on Flickr
Swiss Crop Circle 2009 Aerial, a photo by Kecko on Flickr.

I needed to truncate lines from CSV string prepared for export through mail in my app. Windows Phone mail restricts text size to 1MB. In my app’s case some CSV reports could go beyond 1MB. To make sure that the CSV report does not exceed a desired size in bytes, I created this extension method – SizeIt. SizeIt can truncate lines (and characters if required) from a given string from beginning or from end and also inserts information text line (how many lines and characters removed) in the resulting string. Following is the test form created to show what the method does to a string. In this example a 300 byte 10 line string is given as input and asked to reduce it to <200 bytes, first from beginning, and then from end.

SizeTail

SizeFront

Here is the code for the extension method:

    public static class StringBuilderExtensions
    {
        /// <summary>
        /// Sizes StringBuilder to given size
        /// </summary>
        /// <param name="sb">StringBuilder</param>
        /// <param name="bytes">Maximum Size of resultant string. Mostly the size of string will be less than max size</param>
        /// <param name="removeLinesFromBeginning">Pass true, if clipping has to happen in the beginning</param>
        public static void SizeIt(this StringBuilder sb, int bytes, bool removeLinesFromBeginning)
        {
            bool _stringReplacementHappened = false;
            bool _maxBytesAdjusted_for_lines = false;
            bool _maxBytesAdjusted_for_chars = false;
            string _insertThismessage = "";
            int _linesRemoved = 0;
            int _charsRemoved = 0;
            //try removing lines.
            while (System.Text.Encoding.Unicode.GetByteCount(sb.ToString()) > bytes)
            {
                char _alternateNewLine = '\r';
                string _workingString = sb.ToString();
                if (!_workingString.Contains(Environment.NewLine) && !_workingString.Contains(_alternateNewLine))
                {
                    break;
                }
                int _newlinelocation = sb.ToString().IndexOf(Environment.NewLine);
                int _lastNewlinelocation = sb.ToString().LastIndexOf(Environment.NewLine);
                if (_newlinelocation <= 0)
                {
                    _newlinelocation = sb.ToString().IndexOf(_alternateNewLine);
                }
                if (_lastNewlinelocation <= 0)
                {
                    _lastNewlinelocation = sb.ToString().LastIndexOf(_alternateNewLine);
                }

                if (removeLinesFromBeginning)
                {
                    sb = sb.Remove(0, _newlinelocation + 1);
                    _linesRemoved++;
                    _stringReplacementHappened = true;
                    _insertThismessage = string.Format("... {0} lines removed{1}", _linesRemoved, Environment.NewLine);
                }
                else
                {
                    int _lastReturnAt = _lastNewlinelocation;
                    int _charsInLastLine = sb.Length - _lastNewlinelocation;
                    sb = sb.Remove(_lastReturnAt, _charsInLastLine);
                    _linesRemoved++;
                    _stringReplacementHappened = true;
                    _insertThismessage = string.Format("{1}{0} lines removed...", _linesRemoved, Environment.NewLine);
                }
                if (!_maxBytesAdjusted_for_lines)
                {
                    bytes -= 50; //40 extra bytes for information text
                    _maxBytesAdjusted_for_lines = true;
                }
            }
            //if it's still more than the desired size
            if (System.Text.Encoding.Unicode.GetByteCount(sb.ToString()) > bytes)
            {
                int _currentBytes = System.Text.Encoding.Unicode.GetByteCount(sb.ToString());
                if (!_maxBytesAdjusted_for_lines)
                {
                    bytes -= 100;
                    _maxBytesAdjusted_for_lines = true;
                    _maxBytesAdjusted_for_chars = true;
                }
                else if (!_maxBytesAdjusted_for_chars)
                {
                    bytes -= 50;
                    _maxBytesAdjusted_for_chars = true;
                }
                int _removeChars = (_currentBytes - bytes) / 2;
                if (removeLinesFromBeginning)
                {
                    sb = sb.Remove(0, _removeChars);
                    _stringReplacementHappened = true;
                    _insertThismessage = string.Format("... {0} lines and {1} chars removed{2}", _linesRemoved, _removeChars, Environment.NewLine);
                }
                else
                {
                    sb = sb.Remove(sb.Length - _removeChars, _removeChars);
                    _stringReplacementHappened = true;
                    _insertThismessage = string.Format("{2}{0} lines and {1} chars removed...", _linesRemoved, _removeChars, Environment.NewLine);
                }
            }
            if (_stringReplacementHappened)
            {
                if (removeLinesFromBeginning)
                {
                    sb.Insert(0, _insertThismessage);
                }
                else
                {
                    sb.Append(_insertThismessage);
                }
            }
        }
    }

You may extend SizeIt to make it even smarter because it has following shortcomings in current version:

  • Does not truncate very closely to Max size. Will always be less than 50 bytes from max.
  • If there are only two lines and line of truncating side is huge, even a slight reduction in size will remove the complete content of the huge line, meaning loosing most of the text. This could be taken care of by firsts analyzing the string and then truncating line/character, as required
  • The logic inherently tries to remove lines first, should intelligently decide whether to remove line or chars.

C# : Properties and Serialization

Protected by mikecogh, on Flickr
Protected, a photo by mikecogh on Flickr.

Does your app serialize (on IS or for wire-travelling) model entities to JSON/XML? Did you come across a scenario where you want to ensure that a property is, though serialize-able but should not be settable from the code? For example ID property. Your model class creates this ID internally and it also gets serialized. For it being serialize-able, it is required that the properly is public, but then you expose it to other parts of code as well and make it vulnerable for accidental edits. EditorBrowsableAttribute is not the solution here because of two reasons; one it does not work with the code inside your project, second if it did work, it’s still only hiding from IntelliSense and not stopping you from setting the property in code.
I tackle this scenario in my Nokia Lumia app by having a super-class PersistentEntity and then derive all model classes from PersistentEntity class.
ClassDiagram
PersistentEntity has two properties related to serialization – WriteState and ReadState which are of PersistencyStates enumeration type. PersistencyStates enum has following members – None, WritePending, Queued (because all serialization takes place on BG thread), Writing, Written, Read, and Reading. In case of serializing an entity, as soon as any changes made to the object, the object goes in WritePending, and from there it changes state from Queued to Writing, to Written state. Every other property in derived class which needs to be exposed for serialization only, ensures that in setter sets the value SavingState is “Saving”. If it is not, throw an invalid operation exception with proper message. Let’s have a look at the code.

PersistencyStates enumerator:

    public enum PersistencyStates
    {
        None, WritePending, Queued, Writing, Written, Reading, Read
    }

This is the super class PersistentEntity:

   public class PersistentEntity : ISerializable
    {
        [NonSerialized] //for Silverlight this is IgnoreDataMember attribute
        public PersistencyStates WriteState { get; private set; }
        [NonSerialized] //for Silverlight this is IgnoreDataMember attribute
        public PersistencyStates ReadState { get; private set; }

        #region serialization related methods
        [OnDeserializing]
        public void OnDeSerializingEntity(StreamingContext context)
        {
            this.WriteState = PersistencyStates.Writing;
        }
        [OnDeserialized]
        public void OnDeSerializedEntity(StreamingContext context)
        {
            this.WriteState = PersistencyStates.Written;
            //do something else if required
            this.WriteState = PersistencyStates.None;
        }
        [OnSerializing]
        public void OnSerializingEntity(StreamingContext context)
        {
            this.ReadState = PersistencyStates.Reading;
        }
        [OnSerialized]
        public void OnSerializedEntity(StreamingContext context)
        {
            this.ReadState = PersistencyStates.Read;
        }
        #endregion //serialization related methods


        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            //do nothing;
        }
    }

This is the derived class TestEntity have a property ID which is ensured to update only while serialization:

   class TestEntity : PersistentEntity
    {
        private Guid _id = Guid.NewGuid();
        public Guid ID 
        {
            get 
            {
                return _id;
            }
            set
            {
                if (this.WriteState == PersistencyStates.Writing)
                {
                    _id = value;
                }
                else
                {
                    throw new InvalidOperationException("Cannot set ID from outside", new Exception("MyCompany.MVVM.Model.TestEntity.ID has public setter for the purpose of serialization only. Not designed to be set from outside of the class."))
                }
            }
        }
    }

I hope I could make the intent and implementation clear.

C# Extension: Random Pastel Colors

C# Extension: Random Pastel Colors

In last post we wrote extension method to get complementary color of a given color. In this post we will write some more extension methods to get colors. Co-incidentally India is totally covered in colors because this is the Holi week.
If you want to have a dynamic feel to your app (or some part of your app) you could fill it with random colors. Let’s write following extension methods which return random color with parameterized control over what type of color output you need:

  • Color.GetRandom – This method returns a random color.
  • Color.GetRandom(Brightness Control) – This method returns a random color between given brightness.
  • Color.GetRandomShade – This returns a random color of the Color’s shade.
  • Color.GetRandomShade(Brightness Control) – It returns a random color of given color between given brightness.
  • Color.GetPastelShade() – Returns a pastel shade of given color.

Let’s code.
We will utilize the same code from our last post and extend the ColorExtensions class. To implement aforesaid methods, add following code to the said class

        static Random randomizer = new Random();
        /// <summary>
        /// Returns a pastel shade of the color
        /// </summary>
        /// <param name="source">Source  color</param>
        /// <returns></returns>
        public static Color GetPastelShade(this Color source)
        {
            return (generateColor(source, true, new HSB { H = 0, S = 0.2d, B = 255 }, new HSB { H = 360, S = 0.5d, B = 255 }));
        }
        /// <summary>
        /// Returns a random color
        /// </summary>
        /// <param name="source">Ignored(Use RandomShade to get a shade of given color)</param>
        /// <returns></returns>
        public static Color GetRandom(this Color source)
        {
            return (generateColor(source, false, new HSB { H = 0, S = 0, B = 0 }, new HSB { H = 360, S = 1, B = 255 }));
        }
        /// <summary>
        /// Returns a random color within a brightness boundry
        /// </summary>
        /// <param name="source">Ignored (Use GetRandomShade to get a random shade of the color)</param>
        /// <param name="minBrightness">A valued from 0.0 to 1.0, 0 is darkest and 1 is lightest</param>
        /// <param name="minBrightness">A valued from 0.0 to 1.0</param>
        /// <returns></returns>
        public static Color GetRandom(this Color source, double minBrightness, double maxBrightness)
        {
            if (minBrightness >= 0 && maxBrightness <= 1)
            {
                return (generateColor(source, false, new HSB { H = 0, S = 1 * minBrightness, B = 255 }, new HSB { H = 360, S = 1 * maxBrightness, B = 255 }));
            }
            else
            {
                throw new ArgumentOutOfRangeException();
            }
        }
        /// <summary>
        /// Returns a random shade of the color
        /// </summary>
        /// <param name="source">Base color for the returned shade</param>
        /// <returns></returns>
        public static Color GetRandomShade(this Color source)
        {
            return (generateColor(source, true, new HSB { H = 0, S = 1, B = 0 }, new HSB { H = 360, S = 1, B = 255 }));
        }
        /// <summary>
        /// Returns a random color within a brightness boundry
        /// </summary>
        /// <param name="source">Base color for the returned shade</param>
        /// <param name="minBrightness">A valued from 0.0 to 1.0, 0 is brightest and 1 is lightest</param>
        /// <param name="minBrightness">A valued from 0.0 to 1.0</param>
        /// <returns></returns>
        public static Color GetRandomShade(this Color source, double minBrightness, double maxBrightness)
        {
            if (minBrightness >= 0 && maxBrightness <= 1)
            {
            return (generateColor(source, true, new HSB { H = 0, S = 1 * minBrightness, B = 255 }, new HSB { H = 360, S = 1 * maxBrightness, B = 255 }));
            }
            else
            {
                throw new ArgumentOutOfRangeException();
            }
        }
        /// <summary>
        /// Process parameters and returns a color
        /// </summary>
        /// <param name="source">Color source</param>
        /// <param name="isaShadeOfSource">Should source be used to generate the new color</param>
        /// <param name="min">Minimum range for HSB</param>
        /// <param name="max">Maximum range for HSB</param>
        /// <returns></returns>
        private static Color generateColor(Color source, bool isaShadeOfSource, HSB min, HSB max)
        {
            HSB hsbValues = ConvertToHSB(new RGB { R = source.R, G = source.G, B = source.B });
            double h_double = randomizer.NextDouble();
            double s_double = randomizer.NextDouble();
            double b_double = randomizer.NextDouble();
            if (max.B - min.B == 0) b_double = 0; //do not change Brightness
            if(isaShadeOfSource)
            {
                min.H = hsbValues.H;
                max.H = hsbValues.H;
                h_double = 0;
            }
            hsbValues = new HSB
            {
                H = Convert.ToDouble(randomizer.Next(Convert.ToInt32(min.H), Convert.ToInt32(max.H))) + h_double,
                S = Convert.ToDouble((randomizer.Next(Convert.ToInt32(min.S * 100), Convert.ToInt32(max.S * 100)))/100d),
                B = Convert.ToDouble(randomizer.Next(Convert.ToInt32(min.B), Convert.ToInt32(max.B))) + b_double
            };
            Debug.WriteLine("H:{0} | S:{1} | B:{2} [Min_S:{3} | Max_S{4}]", hsbValues.H,_hsbValues.S,_hsbValues.B, min.S, max.S) ;
            RGB rgbvalues = ConvertToRGB(_hsbValues);
            return new Color { A = source.A, R = (byte)_rgbvalues.R, G = (byte)_rgbvalues.G, B = (byte)_rgbvalues.B };
        }

You could call these methods like so:

Color randomPurplishPastel = Colors.Purple.GetPastelShade();

Here is an example :

My app uses random colors pretty extensively:

Avirall Time Suite | Nokia Lumia 1520

Holi hai!!!

C# Extension: Complementary Color

C# Extension: Complementary Color

In the image above blue & orange are complementary colors, green and red are complementary colors, purple and yellow are complementary colors, etc. The formula is; two colors, placed exactly opposite to each other on color wheel are complementary. You use complementary color combination to highlight a particular item in your design. For example, if most part of your screen has blue color, for important elements on the screen you would choose orange color. Photographers and painters know about importance of complementary color in making their paintings and photographs beautiful:

Complementary Colors by OneEighteen, on Flickr
Complementary Colors, a photo by OneEighteen on Flickr.

In case your app design is dynamic which gives control to the user to choose a dominant color in your app, e.g. background color, you would want to know the contrast color of the chosen color on-the-fly. Particularly in Windows Phone apps, if you incorporate user selected accent color in app design, for some important elements you might need contrast color. For example, in following screenshot of my app, the accent color on the phone is magenta and the color of the hands, auto generated in code, is green.

QSQIn1020Small

This extension method of Color class returns the contrast color:
(HSB and RGB conversion code courtesy, Yi-Lun Luo)

namespace MyCompany.AwesomeExtensions.MediaHelpers
{
    public static class ColorExtensions
    {
        static Random randomizer = new Random();
        public static Color GetContrast(this Color source, bool preserveOpacity)
        {
            Color inputColor = source;
            //if RGB values are close to each other by a diff less than 10%, then if RGB values are lighter side, decrease the blue by 50% (eventually it will increase in conversion below), if RBB values are on darker side, decrease yellow by about 50% (it will increase in conversion)
            byte avgColorValue = (byte)((source.R + source.G + source.B) / 3);
            int diff_r = Math.Abs(source.R - avgColorValue);
            int diff_g = Math.Abs(source.G - avgColorValue);
            int diff_b = Math.Abs(source.B - avgColorValue);
            if (diff_r < 20 && diff_g < 20 && diff_b < 20) //The color is a shade of gray
            {
                if (avgColorValue < 123) //color is dark
                {
                    inputColor.B = 220;
                    inputColor.G = 230;
                    inputColor.R = 50;
                }
                else
                {
                    inputColor.R = 255;
                    inputColor.G = 255;
                    inputColor.B = 50;
                }
            }
            byte sourceAlphaValue = source.A;
            if (!preserveOpacity)
            {
                sourceAlphaValue = Math.Max(source.A, (byte)127); //We don't want contrast color to be more than 50% transparent ever.
            }
            RGB rgb = new RGB { R = inputColor.R, G = inputColor.G, B = inputColor.B };
            HSB hsb = ConvertToHSB(_rgb);
            hsb.H = hsb.H < 180 ! hsb.H + 180 : hsb.H - 180;
            //hsb.B = isColorDark ? 240 : 50; //Added to create dark on light, and light on dark
            rgb = ConvertToRGB(_hsb);
            return new Color { A = sourceAlphaValue, R = rgb.R, G = (byte)rgb.G, B = (byte)rgb.B };
        }
        internal static RGB ConvertToRGB(HSB hsb)
        {
            // By: <a href="http://blogs.msdn.com/b/codefx/archive/2012/02/09/create-a-color-picker-for-windows-phone.aspx" title="MSDN" target="_blank">Yi-Lun Luo</a>
            double chroma = hsb.S * hsb.B;
            double hue2 = hsb.H / 60;
            double x = chroma * (1 - Math.Abs(hue2 % 2 - 1));
            double r1 = 0d;
            double g1 = 0d;
            double b1 = 0d;
            if (hue2 >= 0 && hue2 < 1)
            {
                r1 = chroma;
                g1 = x;
            }
            else if (hue2 >= 1 && hue2 < 2)
            {
                r1 = x;
                g1 = chroma;
            }
            else if (hue2 >= 2 && hue2 < 3)
            {
                g1 = chroma;
                b1 = x;
            }
            else if (hue2 >= 3 && hue2 < 4)
            {
                g1 = x;
                b1 = chroma;
            }
            else if (hue2 >= 4 && hue2 < 5)
            {
                r1 = x;
                b1 = chroma;
            }
            else if (hue2 >= 5 && hue2 <= 6)
            {
                r1 = chroma;
                b1 = x;
            }
            double m = hsb.B - chroma;
            return new RGB()
            {
                R = r1 + m,
                G = g1 + m,
                B = b1 + m
            };
        }
        internal static HSB ConvertToHSB(RGB rgb)
        {
           // By: <a href="http://blogs.msdn.com/b/codefx/archive/2012/02/09/create-a-color-picker-for-windows-phone.aspx" title="MSDN" target="_blank">Yi-Lun Luo</a>
            double r = rgb.R;
            double g = rgb.G;
            double b = rgb.B;

            double max = Max(r, g, b);
            double min = Min(r, g, b);
            double chroma = max - min;
            double hue2 = 0d;
            if (chroma != 0)
            {
                if (max == r)
                {
                    hue2 = (g - b) / chroma;
                }
                else if (max == g)
                {
                    hue2 = (b - r) / chroma + 2;
                }
                else
                {
                    hue2 = (r - g) / chroma + 4;
                }
            }
            double hue = hue2 * 60;
            if (hue < 0)
            {
                hue += 360;
            }
            double brightness = max;
            double saturation = 0;
            if (chroma != 0)
            {
                saturation = chroma / brightness;
            }
            return new HSB()
            {
                H = hue,
                S = saturation,
                B = brightness
            };
        }
        private static double Max(double d1, double d2, double d3)
        {
            if (d1 > d2)
            {
                return Math.Max(d1, d3);
            }
            return Math.Max(d2, d3);
        }
        private static double Min(double d1, double d2, double d3)
        {
            if (d1 < d2)
            {
                return Math.Min(d1, d3);
            }
            return Math.Min(d2, d3);
        }
        internal struct RGB
        {
            internal double R;
            internal double G;
            internal double B;
        }
        internal struct HSB
        {
            internal double H;
            internal double S;
            internal double B;
        }
    }
}

Get working code from my GitHub repository (POCs).