Posts Tagged ‘curve’

3 colour gradient

Wednesday, March 18th, 2009

Recently I noticed a green-red gradient that I was using wasn’t really what I wanted.
green-red gradient

I wanted it to go through yellow. I have made a Vista sidebar gadget in the past that shows different colours of the horizontal percentage bars for CPU and memory usage which faded from green at 0% to yellow at 50% and then faded from yellow at 50% to red at 100%. I had thought this solved the problem until I tried to use that same formula for a gradient, which turned out to be a triangular gradient.
green-yellow-red triangle gradient

The problem here is that there is only yellow at the very peak of the triangle so it looks pinched. From here it is obvious that a curve is needed. I first looked into Bezier curves, as you can join two of them easily by using the same points on both. However, this seemed a bit complicated. I next used a bell curve, which is actually a Gaussian function. This function is e-x2. This worked well and I used it throughout the development of this gradient but after I was finished I realised that a simple Sine wave from 0 to PI would have sufficed (and produces almost exactly the same result as a Gaussian function). A better function would be a Cosine wave from -PI to PI, as this gives a smooth gradient at either end that repeats perfectly. However, this would need to be normalised so that it takes a percent from 0.0 to 1.0 and outputs a value from 0.0 to 1.0 y = (cos((x*2-1)*pi)+1)/2, which is easy to do in a simple Sine 0 to PI because it is done just by multiplying the input by PI.

The key to this is that when it hits the peak, at 50%, it changes from a green-yellow gradient to a red-yellow gradient. The sine function is not used directly to determine the colour but rather to determine where on a simple colour gradient to choose the colour from, which allows it to be used with any combination of colours. The end result is this:
green-yellow-red sine gradient (sine)
green-yellow-red cosine gradient (cosine)

C# code listing (for ASPX) is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using System;
using System.Drawing;
using System.Drawing.Imaging;
 
public partial class PercentBar : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        using (Bitmap bmp = new Bitmap(100, 20, PixelFormat.Format24bppRgb))
        {
            double w = (double)bmp.Width;
            for (int x = 0; x < bmp.Width; x++)
            {
                Color c = GetTriColour(x / w, Color.Lime, Color.Yellow, Color.Red);
                for (int y = 0; y < bmp.Height; y++)
                    bmp.SetPixel(x, y, c);
            }
 
            using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
            {
                bmp.Save(ms, ImageFormat.Png);
                ms.WriteTo(Response.OutputStream);
            }
            Response.ContentType = "image/png";
        }
    }
 
    public static Color GetTriColour(double percent, Color left, Color centre, Color right)
    {
        if (percent < 0 || percent > 1)
            throw new Exception("Percent must be between 0 and 1");
 
        //double weight = Math.Sin(percent * Math.PI);
        double weight = (Math.Cos((percent * 2 - 1) * Math.PI) + 1) / 2;
 
        return GetColourFromLinearGradient(weight,
           percent < 0.5 ? left : right, centre);
    }
 
    public static Color GetColourFromLinearGradient(double percent, Color start, Color end)
    {
        double a, r, g, b;
 
        if (percent < 0 || percent > 1)
            throw new Exception("Percent must be between 0 and 1");
 
        double npercent = 1.0 - percent;
 
        a = Math.Min(start.A, end.A) + Math.Abs(start.A - end.A) * (start.A > end.A ? npercent : percent);
        r = Math.Min(start.R, end.R) + Math.Abs(start.R - end.R) * (start.R > end.R ? npercent : percent);
        g = Math.Min(start.G, end.G) + Math.Abs(start.G - end.G) * (start.G > end.G ? npercent : percent);
        b = Math.Min(start.B, end.B) + Math.Abs(start.B - end.B) * (start.B > end.B ? npercent : percent);
 
        return Color.FromArgb((int)a, (int)r, (int)g, (int)b);
    }
}