h3mm3's blog

Stories from behind the keyboard

  • RSS
  • Twitter

Summary

When I customize a web site template, I always replace the color palette looking for a consistent set of colors. In this series I’ll tell how I managed to speed up the whole process, by building a set of Razor functions and push the Razor syntax inside a CSS stylesheet.

In PART ONE we saw how to create a gradient programmatically in order automate a CSS stylesheet using the Razor engine. We started from a base color in the RGB space and converted it to the HSL space. Given its Lightness we could easily build shades and highlights of the starting color.

Let’s agree on some convention

  • RGB : The space where color are defined by their Red, Green and Blue channels. Both R,G and B ranges from 0 to 255
  • HSL: The space where color are defined by their Hue, Saturation and Lightness. Hue ranges in [0,360), Saturation in [0,240], Lightness in [0,240]
  • Hsb: A transform of the HSL space where s is a normalized Saturation and b (brightness, as Microsoft called it) is a normalized Lightness. H is the same Hue of the HSL space.
  • I use lower case for normalized coordinates, UPPER CASE for classic coordinates.

Traveling back to the RGB space

From the HSL representetion of a color we can build a gradient just varying its Lightness. For instance, starting from Blue (H = 240, S = 240, L=120) we can go DarkBlue (L=65) in the shadows, or light blue (say L=160) in the highlighs. In order to get their HTML codes, we have to convert their HSL coordinates to RGB. A useful method written by Oliver in a Stackoverflow thread builds a System.Drawing.Color from αHsb coordinates (α is the opacity of the color, we can stick it to 255). Here comes his method:

/// 
/// Creates a Color from alpha, hue, saturation and brightness.
/// 
/// The alpha channel value.
/// The hue value.
/// The saturation value.
/// The brightness value.
/// A Color with the given values.
public static Color FromAhsb(int alpha, float hue, float saturation, float brightness)
{
    if (0 > alpha
        || 255 < alpha)
    {
        throw new ArgumentOutOfRangeException(
            "alpha",
            alpha,
            "Value must be within a range of 0 - 255.");
    }

    if (0f > hue
        || 360f < hue)
    {
        throw new ArgumentOutOfRangeException(
            "hue",
            hue,
            "Value must be within a range of 0 - 360.");
    }

    if (0f > saturation
        || 1f < saturation)
    {
        throw new ArgumentOutOfRangeException(
            "saturation",
            saturation,
            "Value must be within a range of 0 - 1.");
    }

    if (0f > brightness
        || 1f < brightness)
    {
        throw new ArgumentOutOfRangeException(
            "brightness",
            brightness,
            "Value must be within a range of 0 - 1.");
    }

    if (0 == saturation)
    {
        return Color.FromArgb(
                            alpha,
                            Convert.ToInt32(brightness * 255),
                            Convert.ToInt32(brightness * 255),
                            Convert.ToInt32(brightness * 255));
    }

    float fMax, fMid, fMin;
    int iSextant, iMax, iMid, iMin;

    if (0.5 < brightness)
    {
        fMax = brightness - (brightness * saturation) + saturation;
        fMin = brightness + (brightness * saturation) - saturation;
    }
    else
    {
        fMax = brightness + (brightness * saturation);
        fMin = brightness - (brightness * saturation);
    }

    iSextant = (int)Math.Floor(hue / 60f);
    if (300f <= hue)
    {
        hue -= 360f;
    }

    hue /= 60f;
    hue -= 2f * (float)Math.Floor(((iSextant + 1f) % 6f) / 2f);
    if (0 == iSextant % 2)
    {
        fMid = (hue * (fMax - fMin)) + fMin;
    }
    else
    {
        fMid = fMin - (hue * (fMax - fMin));
    }

    iMax = Convert.ToInt32(fMax * 255);
    iMid = Convert.ToInt32(fMid * 255);
    iMin = Convert.ToInt32(fMin * 255);

    switch (iSextant)
    {
        case 1:
            return Color.FromArgb(alpha, iMid, iMax, iMin);
        case 2:
            return Color.FromArgb(alpha, iMin, iMax, iMid);
        case 3:
            return Color.FromArgb(alpha, iMin, iMid, iMax);
        case 4:
            return Color.FromArgb(alpha, iMid, iMin, iMax);
        case 5:
            return Color.FromArgb(alpha, iMax, iMin, iMid);
        default:
            return Color.FromArgb(alpha, iMax, iMid, iMin);
    }
}

Oliver’s method sticks to Microsoft naming convention, where “Brightness” is actually a value in the “Lightness” dimensions and ranges from 0 to 1. “Saturation” is normalized in [0,1] too. “Hue” ranges from 0 to 360.

All in all, FromAhsb(α,H,s,b) is the Transformation function we need to convert a color from HSL to RGB (is our metaphorical star gate back to the RGB space, if you remember the travel took in PART ONE).

From System.Drawing.Color to HTML coding

Getting the HTML code of a System.Drawing.Color is easy as calling this method:

var blue = System.Drawing.Color.Blue;
var blueHtml = System.Drawing.ColorTranslator.ToHtml(blue);

So all we have to do at this time is to put all this stuff together and maybe spice the recipe with a bunch of handy color transformation functions.

Towards a dynamic CSS

At this point we know how to start from an RGB color and build a number of gradations. To use these colors in an ASP.NET MVC project we have to inject their HTML codes into the Site.css style sheet. We don’t want to hard code them, since we aim to build a dynamic template, based on some parameter. For instance, the following Razor code builds a palette of five color swatches:

@{
  var basecolor = "#960000";
  var bg = basecolor.Brightness(0.4f); 
  var fg = "black";
  var menu_bg = basecolor.Delta(brightness:0.5f);
  var anchor_fg = basecolor.Brightness(0.15f); 
}

Starting from any base color, I build a background color setting its brightness to 0.4 (that’s a Luminosity of 0.4x240 = 96). The foreground color is black. The other two color swatches are based from the basecolor too.

Delta(..) and Brightness(..) are two handy extension methods that increment any dimension of an Hsb color or set its brightness component to a chosen value; they return the HTML code of the built color for our convenience.

Our dynamic CSS can be a “razor’d” version of the default Stile.css file. Just rename it to Stile.cshtml and write something like this:

/*----------------------------------------------------------
@using MyColorHelpers;
@{    
    var basecolor = "#960000";
    var bg = basecolor.Brightness(0.4f); 
    var fg = "black";
    var menubg = basecolor.Delta(brightness:0.5f);
    var afg = basecolor.Brightness(0.15f); 
}
Date: @DateTime.Now

The base color for this template is @basecolor. If you'd like
to use a different color start by replacing all instances of
@basecolor with your new color.
----------------------------------------------------------*/
body {
    background-color: @bg;
    font-size: .85em;
    font-family: "Trebuchet MS", Verdana, Helvetica, Sans-Serif;
    margin: 0;
    padding: 0;
    color: @fg;
}
/* [...] */

As you can see, Razor syntax and a couple of extension methods is all we need to have a dynamic CSS.

In the following post we will put all this stuff together; happy programming and stay tuned!

No comments: