Find the color of a point in a LinearGradientBrush

January 28, 2008 at 7:54 am | Posted in .Net, C#, Silverlight, WPF | 6 Comments

I became intrigued by the problem of finding the color at any point of a rectangle that has been filled with a LinearGradientBrush.  There is are techniques for doing this by essentially sampling the pixel at a rendered point but I wanted to approach the problem algorithmically.

I developed the following method which receives a rectangle object that has been filled with a LinearGradientBrush and a point relative to the upper left corner of the rectangle and returns the calculated color at that point.  There are no restrictions on the Start/Stop Points or the number of GradientStops.  The ColorInterpolationMode can be either SRgbLinearInterpolation (default) or ScRgbLinearInterpolation.  The GradientSpreadMethod is required to be the default (Pad).

There are two methods below:

Color GetColorAtPoint(Rectangle r, Point p) – finds the color at any point of a LinearGradientBrush filled rectangle.

private Double dist(Point px, Point po, Point pf) – helper method for GetColorAtPoint.

To use, copy both methods to your class and call GetColorAtPoint() with a rectangle and point.

//Calculates the color of a point in a rectangle that is filled
//with a LinearGradientBrush.
private Color GetColorAtPoint(Rectangle theRec, Point thePoint)
{
    //Get properties
    LinearGradientBrush br = (LinearGradientBrush)theRec.Fill;
 
    double y3 = thePoint.Y;
    double x3 = thePoint.X;
 
    double x1 = br.StartPoint.X * theRec.ActualWidth;
    double y1 = br.StartPoint.Y * theRec.ActualHeight;
    Point p1 = new Point(x1, y1); //Starting point
 
    double x2 = br.EndPoint.X * theRec.ActualWidth;
    double y2 = br.EndPoint.Y * theRec.ActualHeight;
    Point p2 = new Point(x2, y2);  //End point
 
    //Calculate intersecting points 
    Point p4 = new Point(); //with tangent
 
    if (y1 == y2) //Horizontal case
    {
        p4 = new Point(x3, y1);
    }
 
    else if (x1 == x2) //Vertical case
    {
        p4 = new Point(x1, y3);
    }
 
    else //Diagnonal case
    {
        double m = (y2 - y1) / (x2 - x1);
        double m2 = -1 / m;
        double b = y1 - m * x1;
        double c = y3 - m2 * x3;
 
        double x4 = (c - b) / (m - m2);
        double y4 = m * x4 + b;
        p4 = new Point(x4, y4);
    }
 
    //Calculate distances relative to the vector start
    double d4 = dist(p4, p1, p2);
    double d2 = dist(p2, p1, p2);
 
    double x = d4 / d2;
 
    //Clip the input if before or after the max/min offset values
    double max = br.GradientStops.Max(n => n.Offset);
    if (x > max)
    {
        x = max;
    }
    double min = br.GradientStops.Min(n => n.Offset);
    if (x < min)
    {
        x = min;
    }
 
    //Find gradient stops that surround the input value
    GradientStop gs0 = br.GradientStops.Where(n => n.Offset <= x).OrderBy(n => n.Offset).Last();
    GradientStop gs1 = br.GradientStops.Where(n => n.Offset >= x).OrderBy(n => n.Offset).First();
 
    float y = 0f;
    if (gs0.Offset != gs1.Offset)
    {
        y = (float)((x - gs0.Offset) / (gs1.Offset - gs0.Offset));
    }
 
    //Interpolate color channels
    Color cx = new Color();
    if (br.ColorInterpolationMode == ColorInterpolationMode.ScRgbLinearInterpolation)
    {
        float aVal = (gs1.Color.ScA - gs0.Color.ScA) * y + gs0.Color.ScA;
        float rVal = (gs1.Color.ScR - gs0.Color.ScR) * y + gs0.Color.ScR;
        float gVal = (gs1.Color.ScG - gs0.Color.ScG) * y + gs0.Color.ScG;
        float bVal = (gs1.Color.ScB - gs0.Color.ScB) * y + gs0.Color.ScB;
        cx = Color.FromScRgb(aVal, rVal, gVal, bVal);
    }
    else
    {
        byte aVal = (byte)((gs1.Color.A - gs0.Color.A) * y + gs0.Color.A);
        byte rVal = (byte)((gs1.Color.R - gs0.Color.R) * y + gs0.Color.R);
        byte gVal = (byte)((gs1.Color.G - gs0.Color.G) * y + gs0.Color.G);
        byte bVal = (byte)((gs1.Color.B - gs0.Color.B) * y + gs0.Color.B);
        cx = Color.FromArgb(aVal, rVal, gVal, bVal);
    }
    return cx;
}
 
//Helper method for GetColorAtPoint
//Returns the signed magnitude of a point on a vector with origin po and pointing to pf
private double dist(Point px, Point po, Point pf)
{
    double d = Math.Sqrt((px.Y - po.Y) * (px.Y - po.Y) + (px.X - po.X) * (px.X - po.X));
    if (((px.Y < po.Y) && (pf.Y > po.Y)) ||
        ((px.Y > po.Y) && (pf.Y < po.Y)) ||
        ((px.Y == po.Y) && (px.X < po.X) && (pf.X > po.X)) ||
        ((px.Y == po.Y) && (px.X > po.X) && (pf.X < po.X)))
    {
        d = -d;
    }
    return d;
}

The method works by projecting the input point onto the vector that is described by the LinearGradientBrush Start/Stop points.  There are special cases for horizontal and vertical lines.  The width of the line is calculated as well as the relative distance of the projected point from the StartPoint (using the dist() method).  The GradientStops are ordered and two stops are found that are the closest the projected point.  The color channel values are interpolated based on the relative position of the projected point and these two stops.  The values are assembled into a color and returned.

You can download a demo of this method here.

About these ads

6 Comments »

RSS feed for comments on this post. TrackBack URI

  1. Is this code public domain?

    • Yes. You may use it for any purpose. Please add a comment line indicating this blog as your source.

      • Hi,

        I’d like to know, how can I “translate” this code for Silverlight, because Silverlight’s Projects do not accept WPF references :S

        If you could help..

        Thaanks!

  2. Hi there, wanted to thank you for this code; it was a great starting point for me. I am colorizing a geospatial “image” of floats that represent some property (like total rainfall, where a pixel is a 1km square on the planet). I was able to modify this code to colorize a float[] into a WritableBitmap based on a user-selectable gradient, and do the colorization on the client in Silverlight. Pretty slick.

  3. Thank you for your post. Really helpful!

  4. I wonder if I have the color how can I get the position?


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com. | The Pool Theme.
Entries and comments feeds.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: