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.

Generating Reports in Visual C# 2008 Express Edition

January 3, 2008 at 7:18 pm | Posted in C#, Visual Studio 2008, WPF | 8 Comments

A common question on the MSDN C# Express forum is how do I generate reports?  That is, how does one get a printout of data?  The Standard and higher editions of Visual Studio have reporting services that allow reports to be easily designed in a WYSIWYG way.  But Express users must either buy a third party application (à la Crystal Reports–about $300 USD) or programmatically create the report using C# and GDI+ Classes.

Now with Visual C# 2008 Express Edition, there is a new option.  Windows Presentation Foundation (WPF) provides a set of classes for converting markup (XAML) to Microsoft’s XML Page Specification (XPS).  XPS documents can then be printed directly from your application or saved an viewed and printed by other applications and platforms.

With this system, there are a couple of options:  First, you can specify the report document directly in C# code much like you would using GDI+.  That is, you could place a textBlock or graphic at a specific x,y coordinate on a page using the properties of these elements.  The second option is almost as good as having a report designer like in the higher Visual Studio editions.  You can use the designer in Visual Studio to layout your report in a WPF grid so you can see exactly how your report should look when it is rendered.  Then it is a very simple matter to pass the name of this grid to XPS generating classes for printing or saving.

Printing and XPS in WPF are covered thoroughly in Chapter 15 of Sells & Griffiths, Programming WPF, O”Reilly.

The following example shows the routine for saving a grid1 (and all that it contains) to an XPS file.

private void button1_Click(object sender, RoutedEventArgs e)
{
    Microsoft.Win32.SaveFileDialog dialog = new Microsoft.Win32.SaveFileDialog();
    if (dialog.ShowDialog() == true)
    {
        string xpsOutputPath = dialog.FileName;
        using (XpsDocument xpsFile = new XpsDocument(xpsOutputPath, FileAccess.Write))
        {
            XpsDocumentWriter xpsdw = XpsDocument.CreateXpsDocumentWriter(xpsFile);
            xpsdw.Write(grid1);
        }
    }

}

 

This routine requires adding a couple of references to the solution: ReachFramework and System.Printing.  You will need to also add some namespace references:

using System.Windows.Xps;
using System.IO;
using Microsoft.Win32;
using System.Windows.Xps.Packaging;

The code for printing directly is not much more complex and is described in the book.

With WPF one should be able to quickly and easily create a rich looking data report that can be printed or saved.

How to add a WPF control library template to Visual C# Express 2008

December 5, 2007 at 9:05 am | Posted in C#, Visual Studio 2008, WPF | 7 Comments

Visual C# Express 2008 does not come with a project template for creating custom controls (.dll files).  You can create your own template by following these steps:

  1. 1. Open Visual C# Express 2008.
  2. 2. Create New WPF Application with name: MyLibrary
  3. 3. Project > Add User Control
  4. 4. In Solution Explorer, Delete Window1.xaml and App.xaml (the corresponding .cs files should be deleted by this step)
  5. 5. Project > MyLibrary Properties > Application > Output Type = Class Library
  6. 6. Build Solution.  There should be no errors and the bin/Release folder should have a MyLibrary.dll file
  7. 7. File > Export Template
  8. 8. Select Project Template then Next
  9. 9. Under Template Options change name to: WPF Control Library and Description: Create WPF Control .dll

The next time you open VS, you will find your newly created template ready to use.

Color Picker for WPF

December 3, 2007 at 8:06 am | Posted in C#, Visual Studio 2008, WPF | Leave a comment

I needed a color picker for WPF and found a simple design by Lee Brimelow here.

I used a bitmap image of a pallet from the Windows Forms ColorPicker dialog.

ColorPick

The problem is that it is not a complete color picker.  The missing variable is luminosity.

I found complete ColorPicker here.

Cider Tricks: Menus, TabPages, ToolBars

November 30, 2007 at 11:21 am | Posted in C#, Cider, Visual Studio, WPF | 1 Comment

The frustrating thing about the WPF Designer in VS2008 (a.k.a. “Cider”) is that it does not support drag and drop placement of items for every control. The first three that I encountered were menus, toolbars and laying out controls on a tab page. The IDE does have some capability to facilitate the design of these items that don’t require exclusive XAML coding. You just have to know the techniques to use them.

Menus

While you can add MenuItem to the toolbox, you can not drag it to the content portion of a Menu control in the designer. The easy way to create Menus is as follows:

1. Drag a Menu control from the toolbox onto the window.

2. Select the Menu control and find the Items property in the Property window. Click on the (…) icon. This will bring up the Collection Editor dialog.

3. Use it to add MenuItems. You can edit the properties of each item (like the Header property) as well as add sub-items by clicking on the Items property of the MenuItem. For the sub-items, you can also add a separator.

ToolBars

Items in a ToolBar can only be added with the XAML editor. Once added, you can edit and move them using the Collection Editor for the ToolBar.

TabControl

You cannot layout controls directly on a TabItem for a TabControl. There are two tricks that are required: use a Grid panel and using the SelectedIndex to put each page in view for layout.

1. Drag a TabControl from the toolbox onto the window.

2. Using the Items propery in the Property window, add your TabItems and enter the Header properties for each one.

3. In the XAML editor, add a Grid to each TabItem like this:

<TabItem>
    <Grid/>
</TabItem>

4. Select the TabControl and locate the SelectedIndex property. Change the index to the page you want to layout.

5. Drag and drop items from the toolbox onto the Grid of the TabItem.

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);
}
Next Page »

Blog at WordPress.com.
Entries and comments feeds.