Find the color of a point in a LinearGradientBrush
January 28, 2008 at 7:54 am | In .Net, C#, Silverlight, WPF | 2 CommentsI 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.
2 Comments »
RSS feed for comments on this post. TrackBack URI
Leave a comment
Blog at WordPress.com. | Theme: Pool by Borja Fernandez.
Entries and comments feeds.
Is this code public domain?
Comment by Edward D'Souza — October 16, 2009 #
Yes. You may use it for any purpose. Please add a comment line indicating this blog as your source.
Comment by dotupdate — October 16, 2009 #