Suppose you have several objects in your WPF application filled with similar Brushes which differ in brightness only. Like in this image:

red_rect

 

The nine rectangles use the same RadialGradienBrush but each rectangle is a little darker than previous. You can create these 9 different brushes in your favorite design tool (Expression Blend, etc.). But what if you need to change the base color later or you just need to make the brush user-configurable? Like in these samples:

 

 green_rect

 

blue_rect

 

The better approach would be to define the base brush once and then change it’s brightness in code. Unfortunately there’s no built-in way to change brightness of the Brush or Color in WPF. This is what we are going to address in this article.

 

Hue, Saturation, Brightness

System.Windows.Media.Color structure represents colors as 4 channels: alpha (opacity) channel and 3 channels for each core color (red, green and blue). aRGB color space is good for representing colors but it’s quite problematic to control such properties as brightness with these 4 channels. We will convert our colors to and from a HSB color space which is good at exactly what we need – controlling such perceptual color characteristics as hue, saturation and brightness. To do this we create a new class called HSBColor defined like this:

   1: public class HSBColor

   2: {

   3:     public double H { get; set; }

   4:     public double S { get; set; }

   5:     public double B { get; set; }

   6:     public byte A { get; set; }

   7: 

   8:     public static HSBColor FromColor(Color rgbColor)

   9:     {

  10:         HSBColor result = new HSBColor();

  11: 

  12:         // preserve alpha

  13:         result.A = rgbColor.A;

  14: 

  15:         // convert R, G, B to numbers from 0 to 1

  16:         double r = rgbColor.R / 255d;

  17:         double g = rgbColor.G / 255d;

  18:         double b = rgbColor.B / 255d;

  19: 

  20:         double max = Math.Max(r, Math.Max(g, b));

  21:         double min = Math.Min(r, Math.Min(g, b));

  22: 

  23:         // hue

  24:         if (max == min)

  25:             result.H = 0;

  26:         else if (max == r)

  27:             result.H = (60 * (g - b) / (max - min) + 360) % 360;

  28:         else if (max == g)

  29:             result.H = 60 * (b - r) / (max - min) + 120;

  30:         else

  31:             result.H = 60 * (r - g) / (max - min) + 240;

  32: 

  33:         // saturation

  34:         if (max == 0)

  35:             result.S = 0;

  36:         else

  37:             result.S = 1 - min / max;

  38: 

  39:         // brightness

  40:         result.B = max;

  41: 

  42:         return result;

  43:     }

  44: 

  45:     public Color ToColor()

  46:     {

  47:         Color result = new Color();

  48: 

  49:         result.A = this.A;

  50: 

  51:         int hi = (int)Math.Floor(this.H / 60) % 6;

  52:         double f = this.H / 60 - Math.Floor(this.H / 60);

  53: 

  54:         double p = this.B * (1 - this.S);

  55:         double q = this.B * (1 - f * this.S);

  56:         double t = this.B * (1 - (1 - f) * this.S);

  57: 

  58:         switch (hi)

  59:         {

  60:             case 0:

  61:                 result.R = (byte)(this.B * 255);

  62:                 result.G = (byte)(t * 255);

  63:                 result.B = (byte)(p * 255);

  64:                 break;

  65:             case 1:

  66:                 result.R = (byte)(q * 255);

  67:                 result.G = (byte)(this.B * 255);

  68:                 result.B = (byte)(p * 255);

  69:                 break;

  70:             case 2:

  71:                 result.R = (byte)(p * 255);

  72:                 result.G = (byte)(this.B * 255);

  73:                 result.B = (byte)(t * 255);

  74:                 break;

  75:             case 3:

  76:                 result.R = (byte)(p * 255);

  77:                 result.G = (byte)(q * 255);

  78:                 result.B = (byte)(this.B * 255);

  79:                 break;

  80:             case 4:

  81:                 result.R = (byte)(t * 255);

  82:                 result.G = (byte)(p * 255);

  83:                 result.B = (byte)(this.B * 255);

  84:                 break;

  85:             case 5:

  86:                 result.R = (byte)(this.B * 255);

  87:                 result.G = (byte)(p * 255);

  88:                 result.B = (byte)(q * 255);

  89:                 break;

  90:         }

  91: 

  92:         return result;

  93:     }

  94: }

The algorithm behind these conversions could be found in this Wikipedia article.

Now all we need to do is modify brightness of every color used in our brushes.

 

SetBrightness() Extension Method

We will create an extension method for Brush class which creates a new brush based on the original brush but with new brightness applied. Our method will support all Color based brushes such as SolidColorBrush, LinearGradientBrush and RadialGradientBrush. For all other brush types our method will just return the clone of the original brush.

With SolidColorBrush we are going to set brightness on the color set in it’s Color property and for GradientBrush(es) – on Color property of each GradientStop. So, enough talking – here’s the code:

   1: public static Brush SetBrightness(this Brush original, double brightness)

   2: {

   3:     if (brightness < 0 || brightness > 1)

   4:         throw new ArgumentOutOfRangeException("brightness",

              "brightness should be between 0 and 1");

   5: 

   6:     Brush result;

   7: 

   8:     if (original is SolidColorBrush)

   9:     {

  10:         HSBColor hsb = HSBColor.FromColor(((SolidColorBrush)original).Color);

  11:         hsb.B = brightness;

  12:         result = new SolidColorBrush(hsb.ToColor());

  13:     }

  14:     else if (original is GradientBrush)

  15:     {

  16:         result = original.Clone();

  17:         // change brightness of every gradient stop

  18:         foreach (GradientStop gs in ((GradientBrush)result).GradientStops)

  19:         {

  20:             HSBColor hsb = HSBColor.FromColor(gs.Color);

  21:             hsb.B = brightness;

  22:             gs.Color = hsb.ToColor();

  23:         }

  24:     }

  25:     else

  26:     {

  27:         result = original.Clone();

  28:     }

  29: 

  30:     return result;

  31: }

 

Using the Method

Let’s look at a simple usage scenario. We will create those 9 rectangles pictured above. The window’s XAML looks like this:

   1: <Window x:Class="Brightness.Window2"

   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   4:     Title="Window2" Height="250" Width="300" Background="#333333"

   5:     Loaded="Window_Loaded">

   6:     <Window.Resources>

   7:         <Style x:Key="RectangleStyle" TargetType="Rectangle">

   8:             <Setter Property="RadiusX" Value="10" />

   9:             <Setter Property="RadiusY" Value="10" />

  10:             <Setter Property="Margin" Value="5" />

  11:         </Style>

  12:     </Window.Resources>

  13:     <UniformGrid x:Name="mainGrid" Margin="30">

  14:         <Rectangle Style="{StaticResource RectangleStyle}">

  15:             <Rectangle.Fill>

  16:                 <RadialGradientBrush GradientOrigin="-0.3 -0.3">

  17:                     <GradientStop Color="#ffffff" Offset="0" />

  18:                     <GradientStop Color="#0000FF" Offset="1" />

  19:                 </RadialGradientBrush>

  20:             </Rectangle.Fill>

  21:         </Rectangle>

  22:         <Rectangle Style="{StaticResource RectangleStyle}" />

  23:         <Rectangle Style="{StaticResource RectangleStyle}" />

  24:         <Rectangle Style="{StaticResource RectangleStyle}" />

  25:         <Rectangle Style="{StaticResource RectangleStyle}" />

  26:         <Rectangle Style="{StaticResource RectangleStyle}" />

  27:         <Rectangle Style="{StaticResource RectangleStyle}" />

  28:         <Rectangle Style="{StaticResource RectangleStyle}" />

  29:         <Rectangle Style="{StaticResource RectangleStyle}" />

  30:     </UniformGrid>

  31: </Window>

The first rectangle has it’s Fill property set to a RadialGradientBrush and all other rectangles have no fill at all. In the Window_Loaded event handler we just take the brush of the first rectangle and apply it to all other rectangles with different brightness.

   1: private void Window_Loaded(object sender, RoutedEventArgs e)

   2: {

   3:     Brush mainBrush = (mainGrid.Children[0] as Rectangle).Fill;

   4:     for (int i = 1; i < 9; i++)

   5:     {

   6:         (mainGrid.Children[i] as Rectangle).Fill = 

               mainBrush.SetBrightness(1 - i / 10d);

   7:     }

   8: }

And that’s it.

 

Final thoughts

Rather than creating an extension method it would be nice to create a custom Brush that would take another “standard” brush and apply specified brightness to it. This would allow animation of Brightness property resulting in nice fade-in and out effects (for example). Unfortunately Microsoft has implemented abstract Brush class in such a way that no 3rd party can inherit from it. Some of the abstract methods in the Brush class are also declared as internal so there’s no way we can implement them. I hope they had a good reason for doing this. Otherwise it doesn’t make much sense.

Tags :

11 Responses to “Changing Brush Brightness in WPF/Silverlight”


  1. Greg

    Said on July 24, 2009 :

    You could make it slightly better by defining an attached property for brush named Brightness, and in the Changed event, use your extension method.

    Code would look something similar to

    DependencyProperty Brightness = new Depe..(,new PropertyMetatdata(1, OnBrightnessChanged);

    public static void OnBrightnessChanged(DependencyObject o, EventArgs e)
    {
    ((Brush)o).SetBrightness(e.NewValue); //Use you extension method.
    }

    This way animation would work, and you could set it from a designer.

  2. Jardar

    Said on July 28, 2009 :

    To have single parameter to adjust from white black this calculation could be used
    if (brightness > 1.0)
    {
    hsb.S = hsb.S – hsb.S * (brightness – 1.0);
    hsb.B = hsb.B + (1.0 – hsb.B) * (brightness – 1.0);
    }
    else
    {
    hsb.S = hsb.S + (1 – hsb.S) * (1.0 – brightness);
    hsb.B = hsb.B – hsb.B * (1.0 – brightness);
    }

  3. Jardar

    Said on July 28, 2009 :

    static public class BrushBehaviour
    {
    public static readonly DependencyProperty BrightnessProperty = DependencyProperty.RegisterAttached(
    “Brightness”,
    typeof(Double),
    typeof(BrushBehaviour),
    new FrameworkPropertyMetadata(1.0, new PropertyChangedCallback(OnBrightnessChanged)));

    private static void OnBrightnessChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
    Brush b = ((Brush)o).SetBrightness((Double)e.NewValue);
    object ob = b.GetValue(SolidColorBrush.ColorProperty);
    o.SetValue(SolidColorBrush.ColorProperty, ob);
    }
    }

  4. Dave

    Said on October 28, 2009 :

    I’m a silverlight (3) newbie, but where do you get the .Clone() method from in your extension method? After some googling I thought Silverlight didn’t have one – or have you written your own?

  5. Alan

    Said on November 13, 2009 :

    You are right. There’s no Clone() in Silverlight. As a workaround you can use a custom made Clone() method. A good one was posted by Justin Angel in Silverlight forums http://forums.silverlight.net/forums/p/1730/51694.aspx

  6. Ryan

    Said on April 13, 2010 :

    Very cool. Here’s my addition (brightness palette generator)…

    public static Brush[] GenerateBrightnessPalette(this Brush BaseBrush, int ColorCount)
    {
    Brush[] palette = new Brush[ColorCount];

    for (int index = 0; index < ColorCount; index++)
    {
    double brightnessFactor = (((double)index + 1.0d) / (double)ColorCount);
    palette[index] = BaseBrush.SetBrightness(brightnessFactor);
    }

    return palette;
    }

  7. torman119

    Said on January 4, 2012 :

    You could also use a custom converter to change the brightness of color in resource dictionaries (e.g to create a palette as resource):

    The converter changes the brightness of the given color based on the ConverterParameter:

    public class BrightnessConverter : IValueConverter
    {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
    return SetBrightness((Color)value, Double.Parse(parameter.ToString(), culture));
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
    return value;
    }
    }

  8. Armandsaph

    Said on March 24, 2014 :

    http://lapopottedannecharlotte.com/a/rayban-sunglass.php This reaction makes no sense. The plan also calls for imposing a 6.5 percent national sales tax, “which collects revenues up and down the income scale,” Minarik said.Ray Ban Shop OnlineIts shares edged 39 cents higher to $45.89.Canadian Natural Resources Ltd. However, this would begin to change in the mid 1950s as federal transfers began to rise. Cheap Ray Bans Glasses
    http://internshipfund.org/wp-content/ghdsale.php This transcript is provided for personal, noncommercial use only, pursuant to our Terms of Use. The supervisor will also advise on such matters as undertaking a literature review, referencing and formatting the thesis, and generally on what should or should not be included in the thesis.GHD AustraliaIndeed, Kahn’s entire approach is refreshingly subtle. Western officials won’t confirm Karzai’s prediction that this year will see a drop in cultivation of nearly a third, but they are expecting a reduction, which they say is urgently needed if efforts to rebuild Afghanistan are to succeed.. GHDs Straighteners
    http://neutrona.com/a/ray-bans-buy.php “Dude, we’re living Call of Duty and it sucks,” says one soldier. The Dow Jones industrials closed down 10.92 points to 16,086.41, the Nasdaq ran ahead 15.14 points to 4,059.89, and the S 500 index was 1.42 points lower at 1,805.81.Womens Ray Ban WayfarerIn agricultural and urban based regions such as the Prairies, the Great Lakes and St. For me that is the place to start.” December 2010 is his deadline for completing elections to the youth wings. Ray Ban 3217
    http://cathedralridgewinery.com/wp-content/uploads/cheapghd.php Britt Dillard and wife Canella of Spartanburg; a daughter, Terri Gowdy and husband Trey of Spartanburg; and four grandchildren, Aubrey Lynne Dillard, Logan Frank Dillard, Harold Watson Gowdy IV, and Abigail Anderson Gowdy.Cheap Ray Ban ShadesLegal work we doing feeds into long term factors. GRIFFITHS, MILDRED PEARL, formerly of the Spike Island section of Moosic, Friday, Thomas P. Dear KMacBoston_MA, I’m very glad you loved our hotel and thanks for your review. GHD Rare
    http://naksit.org/modules/a/ray-ban-shop-online.php I probably have had the signs for the last year but everything I went through there just weren any signs until I had my first hotflash.. Dear Guest,Thank you for choosing Sol Y Mar Sharks Bay Hotel to spend your holidayMy team and I would also like to thank you for your critical and valued feedback.Ray Ban 3343The train case by the way in case you’re wondering has a glossy white finish. Unlike the competition (The Twilight Zone premiered a few weeks after), One Step Beyond featured 30 minute docudramas that were breathlessly promoted as “based on true events” and solely concerned with supernatural activity ghosts, premonitions, psychic phenomena, and so forth.. GHD Straighteners Cheap
    http://skinfare.com/wp-content/uploads/cheapghduk.php These guys, like me, have a passion for sports. Chinsegut Run and Fun Walk: The 15th annual 5K race and 1 mile fun walk takes place on the woodland trails that wind through the old growth, longleaf turkey oak forest; pine woodlands, and oak hammocks.Ray Ban ReviewsPadoan referred to India among the group of countries that “are also using these initiatives and will see significant increases in revenues from tax evaders who have decided to come clean”.. Cheap GHD UK
    http://www.jeffersonvilleuez.com/wp-content/a/ray-ban-sunglasses-shop.php Osago, a journalist for 27 years who was once jailed for criticizing Mr. Lykes Memorial Library, 238 Howell Ave., Brooksville. There is also some question as to what the Bank of Canada will do next week at its scheduled interest rate announcement.Ray Bans Glasses CheapTickets: $12/adults; $9/youth 18 and younger. With a rainy forecast, WSDOT contractor crews couldn’t guarantee that the striping would be crystal clear. Ray Ban Buy Online

3 Trackback(s)

  1. Jul 24, 2009: DotNetBurner - Silverlight
  2. Jul 27, 2009: Silverlight Cream for July 24, 2009 -- #654
  3. Sep 6, 2009: WPF: 90+ Miejsc które warto zna? « Dawid Po?li?ski

Post a Comment