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.

How to make an icon

August 26, 2007 at 6:43 am | Posted in .Net, C# | Leave a comment

Apparently, .Net does not provide a good set of classes to create icons programmatically.  In helping someone on a forum, I ran across a free tool for image processing that does a great job.

The technique uses a free .dll called FreeImage. I gave it a try and with the help of a wrapper found in CodeProject was able to convert a .bmp file to a .ico file.

Here are the steps:

1. Download the FreeImage .dll from here.

2. Copy the .dll into the bin/Debug and/or bin/Release folder in your project

3. Add an Enum and FreeImage Class to your project (shown in the example below).

4. Use three calls to the functions in FreeImage: load the bitmap file, save in icon format, release the resource.

The following example loads a .bmp into FreeImage and saves it back as a .ico file. It includes the required Enum and FreeImage Class. (Requires using System.Runtime.InteropServices; )

class Program
{
    static void Main(string[] args)
    {
        int handle = FreeImage.FreeImage_Load(
            FIF.FIF_BMP,
            @"C:\Documents and Settings\User\Desktop\myBmp.bmp", 
            0);
                      
        FreeImage.FreeImage_Save(
            FIF.FIF_ICO,   
            handle, 
            @"C:\Documents and Settings\User\Desktop\new.ico", 
            0);
 
        FreeImage.FreeImage_Unload(handle);         

    }
}

public enum FIF
{
    FIF_UNKNOWN = -1,
    FIF_BMP = 0,
    FIF_ICO = 1,
    FIF_JPEG = 2,
    FIF_JNG = 3,
    FIF_KOALA = 4,
    FIF_LBM = 5,
    FIF_MNG = 6,
    FIF_PBM = 7,
    FIF_PBMRAW = 8,
    FIF_PCD = 9,
    FIF_PCX = 10,
    FIF_PGM = 11,
    FIF_PGMRAW = 12,
    FIF_PNG = 13,
    FIF_PPM = 14,
    FIF_PPMRAW = 15,
    FIF_RAS = 16,
    FIF_TARGA = 17,
    FIF_TIFF = 18,
    FIF_WBMP = 19,
    FIF_PSD = 20,
    FIF_CUT = 21,
    FIF_IFF = FIF_LBM,
    FIF_XBM = 22,
    FIF_XPM = 23
}
public class FreeImage
{
     [DllImport("FreeImage.dll")]
     public static extern int FreeImage_Load(FIF format, 
                    string filename, int flags);
     
     [DllImport("FreeImage.dll")]
     public static extern void FreeImage_Unload(int handle);
     
     [DllImport("FreeImage.dll")]
     public static extern bool FreeImage_Save(FIF format, 
        int handle, string filename, int flags);
}

Visual Studio 411

August 16, 2007 at 9:14 am | Posted in .Net, C#, Visual Studio | Leave a comment

Visual Studio has a powerful array of features that can greatly boost productivity–especially for new programmers who may not have a full grasp of the .Net framework.  While the C# language itself is quite compact and comprehensible, the number of classes available in the .Net framework is almost mind boggling.  How is a newcomer supposed to remember it all?

The good news is that with Visual Studio you don’t have to.  You just need to know how to get the information (‘411’) when you need it.  Here are the most helpful features for me:

Intellesense

There are three big productivity enhancements that I get out of Intellesense.  First, I use it to help find out what is possible with an object.  By typing a ‘.’ after the object name in a statement, the Intellesense drop down shows all of the properties and methods that are available to me.  Icons by the names of the members indicate the type (properties: a page with a hand, methods: a diamond, events: a lightning bolt).  Second, selecting one of the members gives a brief synopsis which includes the type of value returned and the arguments that are expected.  After selecting, if I type another delimiter (a space, a dot, a parenthesis), Intellesense will  enter the code for me reducing the possibility of making a typographical mistake.  For methods, entering the open parenthesis results in Intellesense showing the possible overloads (different argument possibilities).  Intellesense is almost like having an expert programmer sitting over your shoulder advising you every step of the way.

Dynamic Help

Dynamic help is a way of quickly getting to more information from the MSDN documentation file.  Turn this feature on using menu Help > Dynamic Help.  Now, when you click on an item in the code view or the designer view, a list of links to the Help documentation is presented.  Click on a link and most likely you will get exactly the information you are seeking.

MSDN Documentation

Newcomers need to understand the format of the help documentation so as not to be overwhelmed by its scope.  Every class typically has the following organization: an overview page, a members page listing all properties, methods and events, finally leading to pages for individual members.  More often than not, the individual members page will offer an example that can be adapted or even copied directly into the application.

Index

For me, the most useful way of getting to the information in the documentation (outside of dynamic help) is to use the Index: menu Help > Index.  Also available are ‘Search’ and ‘Content’ but ‘Index’ works best for me.

How do I

Help > How do I can also lead to helpful articles in the documentation for doing the most common tasks like File IO or drawing.

Getting Help Online

Outside of Visual Studio there is a wealth of information.  If I have a question about a control or how to do something, chances are good that someone else has had the same problem and has already posted the solution.  I usually fire up a search engine and put in a few keywords about the problem along with ‘msdn’.  This biases the search to either the msdn documentation or one of the msdn forums.

For me, asking a question in an online forum is my option of last resort.  Not that it is a bad thing to do but the time I spend using the other resources available is always a valuable learning experience in itself and can turn up nuggets that can be used in later projects.

Blog at WordPress.com.
Entries and comments feeds.