Showing posts with label Brushes. Show all posts
Showing posts with label Brushes. Show all posts

Saturday, September 26, 2015

Silverlight and Color

A while back I was looking for a decent color picker for Silverlight - one that didn't require me to actually use an image for the colors since that isn't editable by the user.

I had looked into what it took to create custom colors and brushes dynamically. It was mainly because a client had asked about being able to choose custom colors for a theme. At that time I couldn't figure anything out, however the current project I am on gave me a bit more insight on how I could possibly make the brushes a bit more interactive and dynamic.

Note: the goal was to be able to dynamically change the color without going through a theme, without choosing from a bitmap image and to show the actual color value that reflects the updated color more accurately. So, instead of creating several brushes in a resource dictionary it uses the mathematical color formula (for the HSL color space in this project). 



The linear gradient brush is not scanned in, but created as the app loops through the formulas, using the index value as the hue value, and creates the colors based it and the formulas. 

Most of the formulas came from:
Math behind colorspaces
Math stack exchange
HSL and HSV

I actually found using a Tuple with three items very helpful in calculating the colors

Tuple<double,double,double> rgbColorValues(double hue, double lum, double sat)
        {
 
            double lumValue = lum / 100;
 
            double temp_One = 0;
 
            temp_One = luminousValue(lum, sat);
 
            double temp_Two;
            temp_Two = 2 * lumValue - temp_One;
 
 
            double temp_Red = 0;
            double temp_Green = 0;
            double temp_Blue = 0;
 
            temp_Red = colorTest(hue, "red");
            temp_Green = colorTest(hue, "green");
            temp_Blue = colorTest(hue, "blue");
 
            double colorRed = 0;
            double colorGreen = 0;
            double colorBlue = 0;
 
            colorRed = colorConversion(temp_One, temp_Two, temp_Red);
            colorGreen = colorConversion(temp_One, temp_Two, temp_Green);
            colorBlue = colorConversion(temp_One, temp_Two, temp_Blue);
 
            return Tuple.Create(colorRed, colorGreen, colorBlue);
 
        }

I admit, it should be lightness and I have luminous as the variable, but I am a bit more concerned with getting it to work at the moment :) - and this helps a lot. It brings in the three values used to calculate the color's value: hue (angle), saturation and lightness. Hue is 0-360 while saturation and lightness are both 0-100.

Currently the issue I have is when the Lightness is 50 or higher. I did find a formula that should fix it, however - getting it into the code is challenging without accidentally breaking something - but here is what I currently have that works well for below 50 (and used in the code above)

public double luminousValue(double lumNumber, double satNumber)
        {
            double lumValue = lumNumber / 100;
            double satValue = satNumber / 100;
 
            double tempNumber;
 
            if(lumValue <= 0.5)
            {
                tempNumber = lumValue * (1 + satValue);
            }
            else
            {
                tempNumber = (lumValue + satValue) - (lumValue * satValue);
            }
 
            return tempNumber;
        }


While the hue is the same for all the colors, the difference is the formula used to calculate what the value is for Red, Green and Blue. Luckily a nice little switch statement helps with this:

public double colorTest(double hue, string color)
        {
            double hueValue = hue / 360;
            string colorName = color;
            double tempColor;
            double colorValue;
 
            switch(colorName)
            {
                case"red":
                    tempColor = hueValue + 0.333;
                    break;
                case"green":
                    tempColor = hueValue;
                    break;
                case"blue":
                    tempColor = hueValue - 0.333;
                    break;
                default:
                    tempColor = hueValue;
                    break;
            }
 
            if (tempColor < 0)
            {
                colorValue = tempColor + 1;
            }
            else if(tempColor > 1)
            {
                colorValue = tempColor - 1;
            }
            else
            {
                colorValue = tempColor;
            }
 
            return colorValue;
        }


And the actual conversion is dependent on the value of a certain calculations, and I found putting it in a separate method helped me too:

public double colorConversion(double tempOne, double tempTwo, double colorValue)
        {
 
            double convertedColor;
 
            if(6*colorValue <1.0)
            {
                convertedColor = Math.Round(255*(tempTwo + (tempOne - tempTwo) * 6 * colorValue),2);  
            }
            else if(2*colorValue<1)
            {
                convertedColor = Math.Round(255*tempOne,2);
            }
            else if(3*colorValue<2)
            {
                convertedColor = Math.Round(255*(tempTwo + (tempOne - tempTwo) * (0.667 - colorValue) * 6),2);
            }
            else
            {
                convertedColor = Math.Round(255*tempTwo,2);
            }
 
            return convertedColor;
 
        }

Here is where the brush is built - and a list for the values that gives me access to both the hex value and location in the gradient brush. By creating the list the same time I create the brush - and they are both tied to the value of the hue, I can call up the color value of a specific area in the gradient brush by calling up the corresponding index of the color list. If the value of the hue is 30, it pulls the value from the item with an index of 30 in the list.

public void buildColorDefs()
        {
            LinearGradientBrush colorRangeBrush = new LinearGradientBrush();
            colorRangeBrush.StartPoint = new Point(0, 0);
            colorRangeBrush.EndPoint = new Point(1, 1);
            colorRangeBrush.MappingMode = BrushMappingMode.RelativeToBoundingBox;
 
            IList colorStops = colorRangeBrush.GradientStops;
 
            for (int i = 0; i < 360; i++)
            {
                TextBlock textBlock = new TextBlock();
                textBlock.Text = convertFromHue(i).ToString();
                textBlock.VerticalAlignment = VerticalAlignment.Center;
                textBlock.HorizontalAlignment = HorizontalAlignment.Center;
 
 
                string hexColor = convertFromHue(i).ToString();
                hexColor = hexColor.Replace("#"string.Empty);
 
                SolidColorBrush fillBrush = new SolidColorBrush(
                    Color.FromArgb(
                    Convert.ToByte(hexColor.Substring(0, 2), 16),
                    Convert.ToByte(hexColor.Substring(2, 2), 16),
                    Convert.ToByte(hexColor.Substring(4, 2), 16),
                    Convert.ToByte(hexColor.Substring(6, 2), 16)));
 
                Color newColor = fillBrush.Color;
 
                double stopValue = i / 3.5;
                stopValue = stopValue / 100;
                var indexText = i.ToString();
 
                GradientStop newBrush = new GradientStop();
                newBrush.Color = fillBrush.Color;
                newBrush.Offset = stopValue;
 
                Grid container = new Grid();
                container.HorizontalAlignment = HorizontalAlignment.Stretch;
                container.VerticalAlignment = VerticalAlignment.Stretch;
                container.Margin = new Thickness(0);
                container.MinWidth = 175;
 
                Border background = new Border();
                background.HorizontalAlignment = HorizontalAlignment.Stretch;
                background.VerticalAlignment = VerticalAlignment.Stretch;
                background.Background = fillBrush;
 
                Ellipse listRectangle = new Ellipse();
                listRectangle.Height = 25;
                listRectangle.Width = 25;
                listRectangle.Fill = fillBrush;
 
                StackPanel colorPanel = new StackPanel();
                colorPanel.Orientation = Orientation.Horizontal;
                colorPanel.Children.Add(listRectangle);
                colorPanel.Children.Add(textBlock);
 
                container.Children.Add(background);
                container.Children.Add(colorPanel);
 
                ColorListTwo.Items.Add(container);
 
                previewList.Add(newColor);
                colorStops.Add(newBrush);
 
            }
 
            swatchRectangle.Fill = colorRangeBrush;
            HueSlider.Background = colorRangeBrush;
        }


To make sure that when it loads up the user can see the colors - I went ahead and have the values load up with some defaults.

public void setInitialPreviewColor(object sender, RoutedEventArgs e)
        {
 
            double lum = 50;
            double hue = 0;
            double sat = 100;
 
             var rgbColorTuple = rgbColorValues(hue, lum, sat);
             //var calculatedRGBValues = calcHSLRGBValues(hue, lum, sat);
 
             double colorRed = rgbColorTuple.Item1;
             double colorGreen = rgbColorTuple.Item2;
             double colorBlue = rgbColorTuple.Item3;
 
            RedValueDisplay.Text = colorRed.ToString();
            GreenValueDisplay.Text = colorGreen.ToString();
            BlueValueDisplay.Text = colorBlue.ToString();
 
            SolidColorBrush previewBrush = new SolidColorBrush(Color.FromArgb(255, (byte)rgbColorTuple.Item1, (byte)rgbColorTuple.Item2, (byte)rgbColorTuple.Item3));
            ColorPreview.Fill = previewBrush;
        }


You can download the Silverlight project here. 



Sunday, August 12, 2012

Creating a simple control with dimension

This was the original reason a co-worker suggested I blog some of my work. It is a relatively simple design trick but he really liked it and liked the fact it was easy and didn't require a lot of reworking to get the effect.

Step 1: I created a UserControl that contained a simple shape (in this case a circle) - for this example it will use chrome style brushes. I haven't worked with solid or gradient brushes - that will be later after I figure out the Silverlight charts a bit more. (The charts are for a current assignment, but I do want to catch up on documenting what I have been able to accomplish)
The user control also has a drop shadow applied to the shape - this helps with the final artwork having the look of dimension.
The image above shows the final basic user control.

Step 2. Created a new document (UserControl) where I imported the basic control, and copied and pasted several times. Each subsequent layer reduced using the the transform tool in Blend. (This example uses the preview of Blend 5 available from Microsoft. )
This moved the position of the chrome brushes up slightly, giving the appearance they were still using the same reflection but one was above the other.
Above is a closeup of the positioning of each control layer - they all have the exact same center, but by reducing the size the chrome effect of the brushes moves up slightly. Also, the border has a different chrome style brush than the fill does. The difference is very slight but it helps with the dimensional effect.

Step 3. Layer the control to achieve the desired affect. Below shows the complete dimensional control. When using a "curved" chrome brush - it really helps the effect pop. But, that will be a separate post since I am still catching up on figuring out a bit more coding.



Not the perfect representation, but it is all XAML, and you only have to make the image once to reuse on several other controls or pages. Could also be a good button background.

I have uploaded the files here for you to play with. It includes the brushes as well.