Move and resize multiple shapes in WPF and C#

[multiple shapes]

The example Move and resize multiple rectangles in WPF and C# shows how to let the user move and resize multiple rectangles in a WPF program. This example extends that one to let you move and resize multiple shapes.

See the earlier example for the basic idea.

Instead of storing a list of rectangles that you can move, this example stores a list containing multiple shapes that you can move. The following code declares and initializes the list.

// The shapes that the user can move and resize.
private List<Shape> Shapes;

// Make a list of the shapes that the user can move.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
    Shapes = new List<Shape>();
    foreach (UIElement child in canvas1.Children)
    {
        if (child is Shape)
            Shapes.Add(child as Shape);
    }

    // Reverse the list so the shapes on top come first.
    Shapes.Reverse();
}

The program’s window holds a Canvas control that contains an Ellipse, Polygon, and Rectangle. It also contains a Border to show that you don’t need to let the user move every control.

The window’s Loaded event handler creates the list and then loops through the Canvas control’s children. It adds the Shape controls that it finds to the Shapes list.

After it has examined all of the children, the method reverses the order of the Shapes list so they appear inside the list in top-to-bottom order.

The following method determines what if anything is at a particular point.

// The part of the rectangle under the mouse.
private HitType MouseHitType = HitType.None;

// The shape that was hit.
private Shape HitShape = null;

// If the point is over any shape,
// return the shape and the hit type.
private void FindHit(Point point)
{
    HitShape = null;
    MouseHitType = HitType.None;

    foreach (Shape shape in Shapes)
    {
        MouseHitType = SetHitType(shape, point);
        if (MouseHitType != HitType.None)
        {
            HitShape = shape;
            return;
        }
    }

    // We didn't find a hit.
    return;
}

This method loops through the Shapes list and calls the SetHitType method for each of the controls in the list. If SetHitType finds that the target point is over a shape, then FindHit saves the hit type and the shape hit in form-level variables and exits.

Much of the rest of the program is similar to the earlier example. The SetHitType method compares the mouse’s position to the shape’s left, right, top, and bottom coordinates to see if the point is over one of the shape’s corners, edges, or body.

One big change to the method is how it finds the shape’s left, right, top, and bottom coordinates. The earlier version used Canvas.GetLeft and Canvas.GetTop to get the shape’s left and top coordinates. That works for most shapes but doesn’t work for polygons.

A polygon’s Points list contains the points that make up the polygon. The coordinates of those points are relative to the polygon’s left and top coordinates within the Canvas control, so the program must add the Points coordinates to the left and top values to see where the polygon actually lies.

The new version of the SetHitType method calls the following GetLRTB method to get the shape’s actual position.

// Return the shape's left, right, top, and bottom.
private void GetLRTB(Shape shape,
    out double left, out double right,
    out double top, out double bottom)
{
    if (!(shape is Polygon))
    {
        left = Canvas.GetLeft(shape);
        top = Canvas.GetTop(shape);
        right = left + shape.ActualWidth;
        bottom = top + shape.ActualHeight;
        return;
    }

    // Handle polygons separately.
    Polygon polygon = shape as Polygon;
    left = polygon.Points[0].X;
    right = left;
    top = polygon.Points[0].Y;
    bottom = top;
    foreach (Point point in polygon.Points)
    {
        if (left > point.X) left = point.X;
        if (right < point.X) right = point.X;
        if (top > point.Y) top = point.Y;
        if (bottom < point.Y) bottom = point.Y;
    }

    // Add the polygon's left and top coordinates.
    left += Canvas.GetLeft(shape);
    right += Canvas.GetLeft(shape);
    top += Canvas.GetTop(shape);
    bottom += Canvas.GetTop(shape);
}

If the shape is not a polygon, this method uses the same method used by the previous program. It uses the Canvas class’s GetLeft and GetTop methods to get the shape’s location on the Canvas control. It then adds the shape’s actual width and height to get its right and bottom edges.

If the shape is a polygon, the method loops through its points to find the largest and smallest X and Y coordinates. It then adds the shape’s left and top positions on the Canvas control.

The rest of the program is similar to the earlier example, although it has some problems. First, it doesn’t work with polygons. We need to modify the code to adjust the polygon’s points rather than changing its left and top coordinates on the Canvas control. I’ll fix that in my next post.

Second, the program may think it has hit part of a shape when the mouse is not actually over the shape. For example, when the mouse is over the upper left corner of an ellipse’s bounding rectangle, it is not actually over the ellipse itself. In fact, the mouse may be over another shape that lies below the ellipse and the program will still think the mouse is over the ellipse. This wasn’t a problem when we were moving only rectangles because a rectangle coincides with its bounding rectangle. Right now I don’t see a great need to improve this part of the program so I’m not going to fix this.

Download the example to experiment with it and to see additional details. If you like, try to fix the program so it can move and resize polygons. I’ll show my solution in my next post.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in controls, graphics, wpf, XAML | Tagged , , , , , , , , , , , | 7 Comments

Draw a horizontal compass in C#

[compass]

This example shows how to draw the horizontal compass shown at the bottom of the picture on the right. On the surface this seems like a simple drawing exercise. It mostly is a drawing exercise, although it turned out to not be quite as simple as I had thought it would be. The drawing code itself isn’t too complicated, but figuring out exactly how to come up with that code took a little while.

I also added the circular compass heading display in the middle of the form to make it easier to visualize the horizontal compass’s results. For example, when the horizontal compass 135 degrees, the circular heading display points southeast.

The following sections explain how the program draws the horizontal compass. The later sections describes the code that draws the circular heading display.

DrawCompass

The horizontal compass is just a PictureBox. The following code shows the control’s Paint event handler, which draws the compass.

private void DrawCompass(Graphics gr, int value)
{
    // Draw the background.
    DrawBackground(gr);

    // Draw tick marks.
    using (Font nsew_font = new Font("Arial", 14,
        FontStyle.Italic | FontStyle.Bold))
    {
        using (Font degrees_font = new Font("Arial", 12, FontStyle.Italic))
        {
            DrawTickMarks(gr, CurrentValue, nsew_font, degrees_font);
        }
    }

    // Draw the pointer.
    DrawPointer(gr);
}

This code mostly just calls other methods. First it calls DrawBackground to draw the dark gradient background. The code then creates two fonts, one to draw degree numbers and one to draw N, S, E, and W. It then calls the DrawTickMarks method to draw the compass’s tick marks and text.

The Paint event handler finishes by calling the DrawPointer method to draw the little home plate on the control’s middle top.

DrawBackground

The following code shows the DrawBackground method.

private void DrawBackground(Graphics gr)
{
    int wid = picCompass.ClientSize.Width;
    int hgt = picCompass.ClientSize.Height;
    using (LinearGradientBrush brush =
        new LinearGradientBrush(
            picCompass.ClientRectangle,
            Color.White, Color.Gray, 90))
    {
        ColorBlend color_blend = new ColorBlend();
        color_blend.Colors = new Color[]
        {
            Color.White, Color.Black, Color.Black, Color.White,
        };
        color_blend.Positions = new float[]
        {
            0.0f, 0.3f, 0.8f, 1.0f, 
        };
        brush.InterpolationColors = color_blend;
        gr.FillRectangle(brush, picCompass.ClientRectangle);
    }
}

The most interesting part of this method is the code that creates the brush. The method creates a LinearGradientBrush that shades from white to gray across the body of the control. The final parameter to the brush’s constructor, which has value 90, indicates that the brush should shade vertically (in the direction of 90 degrees) from top to bottom.

After it creates the brush, the code modifies it so it shades from white to black and then back to white. To do that it creates a ColorBlend object and sets that object’s Colors and Positions values. The Colors values indicate colors that the gradient should display. The Positions values indicate the fraction of the distance through the brush where each color appears. For example, the first black color appears 0.3 of the distance through the brush. The values used here make the brush hold the black color between fractions 0.3 and 0.8.

After it has created the brush, the code simply fills the compass’s area with it.

DrawTickMarks

After the call to DrawBackground returns, the DrawCompass method creates two fonts and passes them to the following DrawTickMarks method.

private void DrawTickMarks(Graphics gr, float center_value,
    Font nsew_font, Font degrees_font)
{
    // Set the number of degrees that are visible.
    const int width_in_degrees = 180;

    // Calculate tick geometry.
    const int letter_freq = 90;     // Draw a letter every 90 degrees.
    const int large_tick_freq = 30; // Draw a large tick mark every 30 degrees.
    const int small_tick_freq = 15; // Draw a small tick mark every 15 degrees.
    float large_tick_hgt = picCompass.ClientSize.Height / 5f;
    float small_tick_hgt = large_tick_hgt / 2f;
    float large_tick_y0 = picCompass.ClientSize.Height / 10f;
    float large_tick_y1 = large_tick_y0 + large_tick_hgt;
    float small_tick_y0 = large_tick_y0;
    float small_tick_y1 = small_tick_y0 + small_tick_hgt;

    // Find the center.
    int wid = picCompass.ClientSize.Width;
    float x_mid = wid / 2f;
    float letter_y = large_tick_y1 * 1.2f;

    // Find the width of one degree on the control.
    float pix_per_degree = (float)picCompass.ClientSize.Width / width_in_degrees;

    // Find the value at the left edge of the control.
    float left_value = center_value - (wid / 2f) / pix_per_degree;

    // Find the next smaller multiple of small_tick_freq.
    int degrees = small_tick_freq * (int)(left_value / small_tick_freq);
    degrees -= small_tick_freq;

    // Find the corresponding X position.
    float x = x_mid - (center_value - degrees) * pix_per_degree;

    // Adjust degrees so it is between 1 and 360.
    if (degrees <= 0) degrees += 360;

    // Draw tick marks.
    using (StringFormat sf = new StringFormat())
    {
        sf.Alignment = StringAlignment.Center;

        for (int i = 0; i <= width_in_degrees; i += small_tick_freq)
        {
            // See what we should draw.
            string letter = "";
            if (degrees % letter_freq == 0)
            {
                switch (degrees / letter_freq)
                {
                    case 1:
                        letter = "E";
                        break;
                    case 2:
                        letter = "S";
                        break;
                    case 3:
                        letter = "W";
                        break;
                    case 4:
                        letter = "N";
                        break;
                }
                gr.DrawLine(Pens.White, x, large_tick_y0, x, large_tick_y1);
                gr.DrawString(letter, nsew_font,
                    Brushes.White, new PointF(x, letter_y), sf);
            }
            else if (degrees % large_tick_freq == 0)
            {
                gr.DrawLine(Pens.White, x, large_tick_y0, x, large_tick_y1);
                gr.DrawString(degrees.ToString(), degrees_font,
                    Brushes.White, new PointF(x, letter_y), sf);
            }
            else
            {
                gr.DrawLine(Pens.White, x, small_tick_y0, x, small_tick_y1);
            }

            degrees += small_tick_freq;
            if (degrees > 360) degrees -= 360;

            x += small_tick_freq * pix_per_degree;
        }
    }
}

This method first defines some constants to determine things like tick mark sizes and frequencies. This example draws a small tick mark every 15 degrees, a large tick mark every 30 degrees, and the letters N, S, E, and W every 90 degrees.

The code then finds the center of the control and calculates the size of one degree in pixels.

Next the method needs to draw the tick marks. You could loop from 0 to 360 degrees and draw the appropriate tick marks. You would still need to do some calculations to figure out where 0 degrees belongs on the control.

Depending on the current central value, you might also need to draw another set of tick marks after the first one. For example, if center_value is 340, then the first pass from 0 to 360 degrees will end just past the center of the control and you’ll need to draw another set of tick marks.

Similarly if the central value is small, you’ll need to draw another set of tick marks to the left. For example, if center_value is 10, then the first instance of zero degrees will be just to the left of the center and all of the control to the left of that point will be empty.

This kind of looping would work, but it seems rather cumbersome. This example takes a different approach. It calculates the degree value of the leftmost edge of the control. To do that, it divides the control’s half-width (in pixels) by the number of pixels per degree. That gives the half-width in degrees. It then subtracts that value from center_value to get the degree value at the control’s left edge. Note that this value might be less than zero.

The code then find the next smaller multiple of small_tick_freq. To do that, it divides the left edge degree value left_value by small_tick_freq, truncates the result, and multiples by small_tick_freq. One oddity here is that the (int) operator truncates toward zero. That means if the left_value is less than zero, then the resulting multiple of small_tick_freq is larger than left_value.

We could start drawing there, but I want to start a bit earlier so letters and numbers that are drawn just off the left edge of the control are partly visible. To accomplish that, the method subtracts small_tick_freq from the result.

Now that we know the leftmost degree value that we want to draw, the code calculates the X coordinate of that position. It then adjusts the degrees value if necessary so it lies between 1 and 360 degrees. That is the value that we will draw.

At this point the method is just about ready to draw the tick marks. It creates a StringFormat object to align the text and enters a loop. The loop runs from the 0 to the control’s width in degrees. It increments by small_tick_freq each time it passes through the loop.

If the degree number is a multiple of the letter frequency (90 degrees), then the method draws a large tick mark and the appropriate letter.

If the degree number is not a multiple of the letter frequency and it is a multiple of the large tick mark frequency, then the method draws a large tick mark and the degree number as a string.

If the degree number is not one of those multiples, then the method draws a small tick mark.

At the end of the loop, the method adds small_tick_freq to variable degrees so it knows what degree number to draw next time. It also adds small_tick_freq times the number of pixels per degree to x so it knows where to draw the next tick mark.

DrawPointer

After the DrawBackground and DrawTickMarks methods have finished, the DrawCompass method calls the following DrawPointer method.

private void DrawPointer(Graphics gr)
{
    float y0 = 0;
    float y2 = picCompass.ClientSize.Height / 10f;
    float y1 = y2 / 2f;
    float half_wid = y2;

    // Find the center.
    int wid = picCompass.ClientSize.Width;
    float x_mid = wid / 2f;

    // Define the points.
    PointF[] points =
    {
        new PointF(x_mid - half_wid, y0),
        new PointF(x_mid + half_wid, y0),
        new PointF(x_mid + half_wid, y1),
        new PointF(x_mid, y2),
        new PointF(x_mid - half_wid, y1),
    };
    gr.FillPolygon(Brushes.LightBlue, points);
    gr.DrawPolygon(Pens.Black, points);
}

This method simply creates some points to define the pointer’s shape and then fills and outlines it.

That’s all there is to the horizontal compass. The trickiest part is the DrawTickMarks method.

The next section describes the DrawHeading method that draws the circular heading display.

DrawHeading

The following DrawHeading method draws the circular heading display.

private void DrawHeading(Graphics gr, int value, Font font)
{
    float cx = picHeading.ClientSize.Width / 2f;
    float cy = picHeading.ClientSize.Height / 2f;

    // Draw NSEW.
    float letter_r = Math.Min(cx, cy) * 0.85f;
    string[] letters = { "N", "E", "S", "W" };
    int[] degrees = { 270, 0, 90, 180 };
    for (int i = 0; i < 4; i++)
    {
        float letter_x = letter_r * (float)Math.Cos(DegreesToRadians(degrees[i]));
        float letter_y = letter_r * (float)Math.Sin(DegreesToRadians(degrees[i]));
        PointF point = new PointF(cx + letter_x, cy + letter_y);
        DrawRotatedText(gr, font, Brushes.Black,
            letters[i], point, degrees[i] + 90);
    }

    // Draw tick marks.
    const int large_tick_freq = 30; // Draw a large tick mark every 30 degrees.
    const int small_tick_freq = 15; // Draw a small tick mark every 15 degrees.
    const int tiny_tick_freq = 3;   // Draw a tiny tick mark every 3 degrees.
    float outer_r = letter_r * 0.9f;
    float large_r = outer_r * 0.8f;
    float small_r = outer_r * 0.9f;
    float tiny_r = outer_r * 0.95f;
    using (Pen pen = new Pen(Color.Blue, 3))
    {
        for (int i = tiny_tick_freq; i <= 360; i += tiny_tick_freq)
        {
            float cos = (float)Math.Cos(DegreesToRadians(i));
            float sin = (float)Math.Sin(DegreesToRadians(i));
            float x0 = cx + outer_r * cos;
            float y0 = cy + outer_r * sin;

            float x1, y1;
            if (i % large_tick_freq == 0)
            {
                pen.Width = 3;
                x1 = cx + large_r * cos;
                y1 = cy + large_r * sin;
            }
            else if (i % small_tick_freq == 0)
            {
                pen.Width = 2;
                x1 = cx + small_r * cos;
                y1 = cy + small_r * sin;
            }
            else
            {
                pen.Width = 1;
                x1 = cx + tiny_r * cos;
                y1 = cy + tiny_r * sin;
            }
            gr.DrawLine(pen, x0, y0, x1, y1);
        }
    }

    // Draw the pointer.
    // Rotate 90 degrees so North is at 0.
    double radians = DegreesToRadians(value - 90);

    const int tip_r = 4;
    float pointer_r = large_r * 1.0f;
    float tip_x = cx + pointer_r * (float)Math.Cos(radians);
    float tip_y = cx + pointer_r * (float)Math.Sin(radians);
    float tip_x1 = cx + tip_r * (float)Math.Cos(radians + Math.PI / 2.0);
    float tip_y1 = cy + tip_r * (float)Math.Sin(radians + Math.PI / 2.0);
    float tip_x2 = cx + tip_r * (float)Math.Cos(radians - Math.PI / 2.0);
    float tip_y2 = cy + tip_r * (float)Math.Sin(radians - Math.PI / 2.0);
    PointF[] points =
    {
        new PointF(tip_x, tip_y),
        new PointF(tip_x1, tip_y1),
        new PointF(tip_x2, tip_y2),
    };
    gr.FillPolygon(Brushes.Black, points);

    // Draw the center.
    const int center_r = 6;
    RectangleF rect = new RectangleF(
        cx - center_r, cy - center_r,
        2 * center_r, 2 * center_r);
    gr.FillEllipse(Brushes.LightBlue, rect);
    gr.DrawEllipse(Pens.Black, rect);
}

This method first finds the center of the drawing area. It then loops through the letters N, S, E, and W and calls the DrawRotatedText method to draw those letters at their correct compass points. I’ll describe that method shortly.

After drawing the letters, the method loops around the circle drawing tick marks. It uses a technique similar to the one used earlier to draw tick marks of different sizes at various multiples of certain frequencies. This time it draws large, small, and tiny tick marks.

After drawing the tick marks, the method draws the pointer. Both that code and the code that draws the tick marks use sine and cosines to figure out where each line segment should end. The code is somewhat long, but it’s relatively straightforward. It’s mostly long because it uses a lot of constants to define things like the size of the tick marks.

The following code shows the DrawRotatedText method.

private void DrawRotatedText(Graphics gr, Font font, Brush brush,
    string text, PointF location, float degrees)
{
    GraphicsState state = gr.Save();
    gr.ResetTransform();
    gr.RotateTransform(degrees);
    gr.TranslateTransform(location.X, location.Y, MatrixOrder.Append);
    using (StringFormat sf = new StringFormat())
    {
        sf.Alignment = StringAlignment.Center;
        sf.LineAlignment = StringAlignment.Center;
        gr.DrawString(text, font, brush, 0, 0, sf);
    }
    gr.Restore(state);
}

This method saves the Graphics object’s graphical state and then resets its transform.

It then make the Graphics rotate the desired number of degrees. It follows that with a second transformation that moves the resulting graphic so the origin is positioned at the point (x, y).

Next the code creates a StringFormat object that centers text vertically and horizontally. The method then draws the desired text at the origin. The StringFormat object centers the text and then the Graphics object’s transformations rotate the text and translate it to the desired position.

The method finishes by restoring the Graphics object’s saved state.

Conclusion

You may never need this kind of horizontal compass or circular heading display, but you could use something similar to provide some interesting gauges. In any case this example provides some interesting exercises in figuring out how to draw different kinds of graphics.

Download the example to see additional details and to experiment with the program. The DrawRotatedText method may be worth the effort.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in drawing, graphics | Tagged , , , , , , , , | 1 Comment

Load enums at runtime to understand them and their attributes in C#

[load enums]

This example shows how you can load enums from a file at runtime. It demonstrates several useful techniques including:

  • Defining custom attributes
  • Defining attributes with multiple parameters
  • Creating attributes at both design time and run time
  • Loading code at runtime
  • Using reflection to get information about enums
  • Using reflection to get information attributes

Creating Custom Attributes

Custom attributes ate simply classes that are derived from the Attribute class. They can also optionally be decorated with the AttributeUsage attribute to restrict the kinds of things to which they can be applied. For example, the following code defines a Salary attribute.


[System.AttributeUsage(System.AttributeTargets.Field)]
public class Salary : System.Attribute
{
    public float Value;
    public Salary(float value)
    {
        Value = value;
    }
}

The AttributeUsage attribute indicates that the Salary attribute can only be applied to fields. The values defined by an enum are fields, so you can apply this attribute to them.

For example, the following code defines a simple enum that demonstrates the Salary attribute.

enum Jobs
{
    [Salary(12)] DishWasher,
    [Salary(14)] Waiter,
    Manager,
}

Notice that the attribute is not required, and the enum omits it for the Manager value.

Defining Attributes at Runtime

The example program’s code contains definitions for the following three attribute classes.

  • JobTitle(string value)
  • Salary(float value)
  • IsExempt(bool value)

The test text file test_enum.cs uses the following code to define a fourth attribute class, CustomerType.

[System.AttributeUsage(System.AttributeTargets.Field)]
public class CustomerType : System.Attribute
{
    public string TypeName;
    public float Discount;
    public CustomerType(string type_name, float discount)
    {
        TypeName = type_name;
        Discount = discount;
    }
}

The program loads this attribute type at runtime when you load the file. (More about that later.)

Notice that this attribute’s constructor takes two parameters: type_name, which is a string, and discount, which is a float.

The test_enum.cs file uses the attributes in the following code, which defines two enumerations.

// Enums.
enum Employees
{
    [IsExempt(false)]
    Clerk,
    [JobTitle("Super"), IsExempt(false)]
    Supervisor,
    [JobTitle("Manager"), Salary(84000), IsExempt(true)]
    Manager,
    [JobTitle("Admin"), Salary(72000), IsExempt(true)]
    Administrator,
}

enum Customers
{
    [CustomerType("Retail", 0)]
    Customer,
    [CustomerType("Frequent", 10)]
    FrequentBuyer,
    [CustomerType("Wholesale", 40)]
    Wholesale,
}

There are several interesting things to note about this code. First, the Employees values do not all use every attribute. The Clerk value does not use JobTitle or Salary, and Supervisor does not use Salary.

Second, the Customers values use the two-parameter CustomerType attributes.

Finally, note that only the CustomerType attribute is defined inside this file; the other attributes are defined inside the main example program.

Loading Code at Runtime

When you open the example’s File menu and select Open Enum File, the program executes the following code.

// Load and compile an enum file.
private void mnuFileOpenEnumFile_Click(object sender, EventArgs e)
{
    if (ofdEnum.ShowDialog() != DialogResult.OK) return;

    trvEnums.Nodes.Clear();

    // Use the C# DOM provider.
    CodeDomProvider code_provider =
        CodeDomProvider.CreateProvider("C#");

    // Generate a non-executable assembly in memory.
    CompilerParameters parameters = new CompilerParameters();
    parameters.GenerateInMemory = true;
    parameters.GenerateExecutable = false;

    // Get information about this assembly.
    // (So the enum can use attributes defined
    // in Attributes.cs.)
    string exe_name = Assembly.GetEntryAssembly().Location;
    parameters.ReferencedAssemblies.Add(exe_name);

    // Load the enum file's text placed
    // inside our namespace.
    StringBuilder sb = new StringBuilder();
    sb.AppendLine("namespace howto_load_runtime_enum");
    sb.AppendLine("{");
    sb.AppendLine(File.ReadAllText(ofdEnum.FileName));
    sb.AppendLine("}");

    // Compile the code.
    CompilerResults results =
        code_provider.CompileAssemblyFromSource(parameters,
            sb.ToString());

    // If there are errors, display them.
    if (results.Errors.Count > 0)
    {
        trvEnums.Nodes.Add("Error compiling the expression.");
        foreach (CompilerError compiler_error in results.Errors)
        {
            trvEnums.Nodes.Add(compiler_error.ErrorText);
        }
        return;
    }
    ...

This code displays the ofdEnum OpenFileDialog to let you select a code file. If you cancel the dialog, then the code returns without doing anything else.

If you select a file, the code clears the trvEnums TreeView control and then loads the code file.

The code first creates a CodeDomProvider to work with the C# language. The languages that are available depend on which ones are installed on your system and may include C#, Visual Basic, C++, and JScript. In theory a compiler vendor could provide support for other languages.

Next the program defines compiler parameters to tell the code provider to generate compiled code in memory (as opposed to on disk) and to not create an executable (we are only loading a code fragment).

The code then adds the example program’s assembly to the compiler parameters. That allows the code fragment that we will load to use things that are defined by the example program’s code. In particular that allows the fragment to use the attribute classes defined by the example.

Now the program creates a string holding the code that we want to compile. To do that it creates a StringBuilder. It reads the selected file into the StringBuilder, wrapping it in a namespace block so the result looks like this.

namespace howto_load_runtime_enum
{
    ...file contents...
}

The namespace howto_load_runtime_enum is the one that contains the example program’s code. If the code fragment were not in the same namespace as the example program, then it could not use the attributes defined by the example.

Next the program calls the code provider’s CompileAssemblyFromSource method to compile the code string that we built. If there are errors, the code loops through them and adds them to the TreeView control. (Try changing the namespace placed inside the StringBuilder to see some errors.)

If the program gets this far successfully, the rest of the menu item’s event handler uses reflection to examine the enums.

Using Reflection

Before I show you the code that examines the enums, I want to describe the basic approach.

The code fragment in the file test_enum.cs defines the CustomerType attribute and the two enums Employees and Customers. The general approach to learning about the enums is to loop through the types defined by the code fragment and examine those that are enums.

Each enum defines several values. Those are literal values, which means they are defined at design time. For each enum, the code loops through the enum’s fields looking for those that are literal values.

Each literal value represents one of the values defined by the enum. For example, the Employees enum defines the literals Clerk, Supervisor, Manager, and Administrator.

Each literal value can have custom attributes. The code loops through those attributes to study them.

An object’s attribute is an instance of an attribute class. For example, the Clerk value has as an attribute a IsExempt object that was created with parameter false. To understand a value’s attributes, the program must get the values stored in those attribute objects. In the Clerk example, the code needs to get the false that is stored inside the IsExempt field.

Note that the values inside the attribute objects are stored in fields. For example, here’s the CustomerType attribute class again.

[System.AttributeUsage(System.AttributeTargets.Field)]
public class CustomerType : System.Attribute
{
    public string TypeName;
    public float Discount;
    public CustomerType(string type_name, float discount)
    {
        TypeName = type_name;
        Discount = discount;
    }
}

The values TypeName and Discount are stored in public fields within the class.

To get the attribute object’s values, the code uses reflection to loop through the attribute object’s fields. (If the values were stored as properties, you would loop through the object’s properties instead of its fields.)

Finally the program can look at the field information to get the attribute’s values.

To summarize, the code uses the following approach to learn about the file’s enums.

  • For each object defined by the code fragment:
    • If the object is an enum:
      • Add the enum’s name to the TreeView
      • For each field defined by the enum:
        • If the field is a literal:
          • Add the field’s name to the TreeView.
          • For each custom attribute held by the literal field:
            • Loop through the attribute’s fields. For each attribute field:
              • Add the field’s name and value to the attribute’s parameter list.
            • Add the attribute name and parameter list to the TreeView.

The following code shows how the program performs those steps.

    ...
    // See what enumerations there are.
    foreach (Type type in results.CompiledAssembly.GetTypes())
    {
        // Only consider types that are enums.
        if (type.IsEnum)
        {
            // Add a TreeView root node for the enum.
            TreeNode enum_node = trvEnums.Nodes.Add(type.Name);

            // Enumerate the Enum's fields (values).
            FieldInfo[] enum_value_infos = type.GetFields();

            // Loop over the enum's values.
            foreach (FieldInfo value_info in enum_value_infos)
            {
                // See if this is a literal value (set at compile time).
                if (value_info.IsLiteral)
                {
                    // Add it to the Enum's root node.
                    TreeNode value_node =
                        enum_node.Nodes.Add(
                            value_info.Name + " = " +
                            ((int)value_info.GetValue(null)).ToString());

                    // Loop through the value's attributes.
                    foreach (Attribute attr in value_info.GetCustomAttributes(false))
                    {
                        // Start with the attribute's name.
                        string attr_text = attr.GetType().Name + "(";

                        // Add the attribute's fields.
                        foreach (FieldInfo field_info in attr.GetType().GetFields())
                        {
                            attr_text +=
                                field_info.Name + " = " +
                                field_info.GetValue(attr).ToString() +
                                ", ";
                        }

                        // If we have fields, remove the trailing ", ".
                        if (attr_text.Length > 0)
                            attr_text = attr_text.Substring(0, attr_text.Length - 2);
                        attr_text += ")";

                        // Add this to the TreeView.
                        value_node.Nodes.Add(attr_text);
                    }
                }
            }
        }
    }
    trvEnums.ExpandAll();
}

This code is somewhat confusing but only because it’s not obvious which reflection methods you need to use to perform the steps given above. Walk through the code while referring to those steps to see how it works.

Conclusion

The chances that you’ll need to do exactly this are relatively small, but this example provides a useful exercise in reflection. It also demonstrates a few important techniques such as creating custom attribute classes and creating attribute classes that take multiple parameters. One of the less obvious results of this example is that the code loaded at runtime can use classes and other items that are defined by the example program. That’s particularly useful if you want to load scripts from text files and then have them interact with the program’s objects.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in enums, reflection | Tagged , , , , , , , | Leave a comment

Draw an image circle in C#


[image circle]

This example is somewhat similar to the post Draw an image spiral in C# except it draws an image circle instead of an image spiral.

The key is the DrawImageCircle method, which draws a smaller image in a circle on top of a background color or image. Before I describe the method’s body, I want to explain its parameters.

DrawImageCircle Parameters

The following code shows the DrawImageCircle method’s signature.

// Draw the circle of images.
private void DrawImageCircle(Graphics gr,
    Bitmap image,
    int pic_width, int pic_height,
    int img_width, int img_height,
    double offset_multiple,
    double initial_rotation,
    int num_images, double radius)
{
    ...
}

The method’s gr parameter gives the Graphics object on which the method should draw. You can draw a background on the object before you pass it into this method. You can also pass the same Graphics object into the method several times to draw multiple image circles on the same picture.

The pic_width and pic_height parameters indicate how big the final picture should be.

The img_width and img_height parameters give a desired size for the smaller image that should be drawn repeatedly in an image circle. The method draws the image as large as possible without distorting it and making it fit in the indicated width and height.

The offset_multiple value indicates the number of multiples of the angle between images that should be added to the first image’s position. For example, suppose you want to draw six ships in an image circle. Then there are 360 / 6 = 30 degrees between each ship. If offset_multiple is 0.5, then the first ship is drawn 0.5 * 60 = 30 degrees around the circle. This is mostly useful if you want to draw two (or more) different alternating images around the circle. For example, you could draw six spaceships with offset_multiple = 0 and then six asteroids with offset_multiple = 0.5. The result will alternate between spaceships and asteroid around the circle.


[image circle]

The initial_rotation parameter indicates the amount by which the first image should be rotated. The first image is the one on the right side of the circle. The initial_rotation value represents rotation clockwise. For example, the spaceship picture shows a vertical spaceship. Setting initial_rotation to zero makes the rightmost image show a vertical spaceship. If you set initial_rotation to 45, you get the picture shown on the right.

The num_images parameter indicates the number of times the smaller image should appear spaced equally around the image circle.

Finally, radius gives the distance from the center of the picture to the center of the smaller images.

DrawImageCircle Body

Now that you understand the DrawImageCircle method’s parameters, here’s the entire method.

// Draw the circle of images.
private void DrawImageCircle(Graphics gr, Bitmap image,
    int pic_width, int pic_height,
    int img_width, int img_height,
    double offset_multiple, double initial_rotation,
    int num_images, double radius)
{
    GraphicsState state = gr.Save();

    // Get the picture's center.
    float cx = pic_width / 2f;
    float cy = pic_height / 2f;

    // Adjust the image size to preserve aspect ratio.
    double scale_x = (double)img_width / image.Width;
    double scale_y = (double)img_width / image.Height;
    double scale = Math.Min(scale_x, scale_y);
    img_width = (int)(image.Width * scale);
    img_height = (int)(image.Height * scale);

    // Get the image's source rectangle.
    RectangleF src_rect = new RectangleF(
        0, 0, image.Width, image.Height);

    // Make destination points to center the image at the origin.
    PointF[] dest_points =
    {
        new PointF(-img_width / 2f, -img_height / 2f),
        new PointF( img_width / 2f, -img_height / 2f),
        new PointF(-img_width / 2f,  img_height / 2f),
    };

    // Loop through the images.
    double dtheta = 360 / num_images;
    double theta = dtheta * offset_multiple;
    double angle = initial_rotation + theta;
    for (int i = 0; i < num_images; i++)
    {
        // Get the point where the image's center should be drawn.
        double x = cx + radius * Math.Cos(theta * Math.PI / 180);
        double y = cy + radius * Math.Sin(theta * Math.PI / 180);
        PointF point = new PointF((float)x,(float)y);

        // Rotate and then translate to (x, y).
        gr.ResetTransform();
        gr.RotateTransform((float)angle);
        gr.TranslateTransform((float)x, (float)y, MatrixOrder.Append);

        // Draw the image.
        gr.DrawImage(image, dest_points, src_rect, GraphicsUnit.Pixel);

        theta += dtheta;
        angle += dtheta;
    }

    gr.Restore(state);
}

The method first saves the state of the Graphics object so it can restore it later. It then finds the image’s center.

Next the code calculates horizontal and vertical scales that would map the smaller image to the indicated img_width and img_height values. It uses the smaller of the two scales to make the image fit within the desired dimensions without distorting the image. The method uses the scale to recalculate the image’s img_width and img_height.

Later the code will draw the image centered at the origin and use rotations and translations to move the image to its correct position on the image circle. To make drawing the image at the origin easier, it calculates the necessary source rectangle and destination points. The source rectangle includes the image’s entire area. The destination points define the upper left, upper right, and lower left corners of the image (at its scaled size) when it is drawn centered at the origin.

Now the code enters a loop to draw the desired number of images. The value dtheta is the angle in degrees between the images as measured from the center of the picture. The variable theta gives each image’s angle with respect to the center. The program initializes theta to the initial_rotation value times dtheta.

The variable angle indicates the angle by which an image should be rotated. Initially this value is set to the indicated initial_rotation value plus the initial value of theta. That rotates the image appropriately if initial_rotation is not zero.

Inside the loop the program uses sines and cosines (with angles measured in radians) to determine where the next instance of the smaller image should be drawn. The method resets the Graphics object’s transformations. It then rotates the image by angle degrees and translates the origin to the image’s desired position.

Next the code draws the image centered at the origin. The rotation and translation transformations move the result onto the image circle.

After it has drawn the image, the code adds dtheta to theta and angle so the next image will be correctly rotated and positioned.

After the loop ends, the method restores the Graphics object’s state to the way it was before the method installed the various rotations and translations.

Conclusion

As usual the example program has many details that are not described here. Loading the background color or picture, loading the smaller picture, saving the resulting picture into a file, and validating the parameters that you enter on the form are all important tasks, but they are relatively straightforward so they aren’t explained here.

Download the example to see additional details and to experiment with the program. By calling the method multiple times with different images and parameters, you can make some fairly complex images such as the one below.


[image circle]


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in drawing, graphics, image processing, mathematics | Tagged , , , , , , , , , | 1 Comment

Use the ColorMatrix and ImageAttributes classes to quickly modify image colors in C#

[modify image colors]

This example shows how to use the ColorMatrix and ImageAttribute classes to quickly modify image colors. I’ve made several posts that use those classes to modify image colors in a few specific ways. I’ve also posted other posts that modify colors by looping through an image’s pixels.

This post describes the ColorMatrix and ImageAttribute classes and explains how you can use them to modify image colors in certain ways extremely quickly.

Using ColorMatrix and ImageAttributes

When you use the Graphics class’s DrawImage method, you can pass in an optional ImageAttributes object that can modify the image. For example, the following code snippet applies the ImageAttributes object named attr to the image that is being drawn.

gr.DrawImage(image, dest_points,
     source_rect, GraphicsUnit.Pixel, attr);

One of the things that the ImageAttributes object can do is apply a ColorMatrix to the image’s pixels. The ColorMatrix is a five-by-five matrix of values that are multiplied by each of the image’s pixels. Each pixel is treated as a vector of values that include its red, green, blue, and alpha (opacity) components, plus a fifth value that is set to 1.

The following picture shows how a pixel’s component vector is multiplied by the matrix.


[modify image colors]

The system performs normal vector/matrix multiplication to get the result pixel’s component values. For example, multiplying the vector by the matrix’s first column gives the red component.

    red = R * m00 + G * m10 + B * m20 + A * m30 + 1 * m40

Building the ColorMatrix

There are several ways that you can build a ColorMatrix. The following code shows how you can explicitly set each of the object’s matrix entries.

ColorMatrix color_matrix = new ColorMatrix(
    new float[][]
    {
        new float[] {-1,  0,  0,  0,  0},
        new float[] { 0, -1,  0,  0,  0},
        new float[] { 0,  0, -1,  0,  0},
        new float[] { 0,  0,  0,  1,  0},
        new float[] { 1,  1,  1,  0,  1}
    });

If you multiply the pixel component vector by this matrix, the new pixel value is <1 – R, 1 – G, 1 – B, A, 1>.

That result inverts the pixel’s red, green, and blue color components so you get a negative of the original image. Shortly I’ll talk more about the component values and why values such as 1 – R inverts the red component. I’ll also show several specific matrices that you might want to use.

Another approach to building a ColorMatrix is to start with the default identity matrix and then modify any entries that you want to change. The following code demonstrates that approach.

ColorMatrix color_matrix = new ColorMatrix();
color_matrix.Matrix33 = 0.75f;

The first statement initializes the matrix to a default, which is the identity matrix. that matrix, which is all zeros except for all ones on the upper-left to lower-right diagonal, is shown in the following picture. If you multiply a pixel’s component vector by an identity matrix, the result is the same as the original vector.


[modify image colors]

The code snippet then sets the matrix’s [3][3] entry to 0.75 to give the following matrix. Now if you component multiply a vector by the matrix, the vector’s alpha component is multiplied by 0.75 so the result is less opaque.


[modify image colors]

Component Values

Often we think of a pixel’s component values as ranging from 0 to 255, but that really only makes sense if you’re using a color model that uses 8 bits for each color component. These days many images use 24-bit color with 8 bits for each of the red, green, and blue components. Other images use 32 bits with 8 bits for red, green, blue, and alpha. In theory, however, there’s no reason why you couldn’t use some other color model. Older images may use color models with fewer bits such as 8, 15, 16, or 18 bits. Deep color models may use more bits such as 30, 36, or 48 bits.

To make working with colors as flexible as possible, a ColorMatrix represents each color component as a value between zero and one. For example, if you’re using a 32-bit color system and a pixel has RGBA values (255, 128, 0, 255), then the color component vector used by the ColorMatrix is <1, 0.5, 0, 1>.

One consequence of this is that the values in a ColorMatrix are usually between zero and one. You can use larger or smaller values, but they may not make sense.

Note that the ColorMatrix pegs the resulting component values so they lie between zero and one. If a result of the matrix multiplication is less than zero, then the output color component is zero. Similarly if the result is greater than one, the result is one.

Useful Matrices

The following picture shows some useful matrices.


[modify image colors]
[modify image colors]
[modify image colors]
[modify image colors]
[modify image colors]
[modify image colors]
[modify image colors]
[modify image colors]

You can use similar matrices to adjust different color components. For example, take a look at the “Set Alpha” matrix. To use a similar matrix to set red values, start with an identity matrix, set the [0][0] entry to 0, and set the [4][0] entry to the value that you want to use for the red component as shown in the following picture.


[modify image colors]

There are many other ways that you can use the ColorMatrix. For example, you could set the red and green components to the average of those values and set blue components to zero. Or you could switch the image’s red and blue components. You can also use techniques used in three-dimensional graphics to rotate, translate, or skew the colors, although exactly what it means to rotate or skew colors isn’t obvious. Feel free to download the example and experiment.

The Example Program

When you load an image file, the program saves it in the Image variable named TransformedImage. When you apply the commands in the program’s Picture menu, the program performs those commands on that image and displays the result.

Probably the most interesting part of the example program is the following ApplyColorMatrix method, which copies an image into a new bitmap while applying a ColorMatrix to it.


private Image ApplyColorMatrix(Image image, ColorMatrix color_matrix)
{
    // Make the result bitmap.
    Bitmap bm = new Bitmap(image.Width, image.Height);
    using (Graphics gr = Graphics.FromImage(bm))
    {
        // Create an ImageAttributes object and use it to
        // draw the original image onto the result image.
        ImageAttributes attr = new ImageAttributes();
        attr.SetColorMatrix(color_matrix);

        Point[] dest_points =
        {
            new Point(0, 0),
            new Point(image.Width, 0),
            new Point(0, image.Height),
        };
        Rectangle source_rect = new Rectangle(
            0, 0, image.Width, image.Height);
        gr.DrawImage(image, dest_points,
            source_rect, GraphicsUnit.Pixel, attr);
    }
    return bm;
}

The method first creates a new bitmap with the same size as the original image and makes an associated Graphics object. It then creates a new ImageAttributes object and sets its ColorMatrix to the one passed in as a parameter.

The code then creates an array giving the upper left, upper right, and lower left corners of the rectangle where the image should be drawn. It also creates a source rectangle indicating the part of the image that should be drawn. The values used here copy the entire input image onto the entire output image.

Next the method draws the input image onto the new bitmap, passing the DrawImage method the ImageAttributes object. (There are simpler overloaded versions of the DrawImage method, but they don’t include an ImageAttributes object.

The method finishes by returning the new bitmap.

The example program uses the ApplyColorMatrix method to perform each of its operations. For example, the following code shows how the program decreases the picture’s alpha value.

private void mnuPictureDecreaseAlpha_Click(object sender, EventArgs e)
{
    ColorMatrix color_matrix = new ColorMatrix(
        new float[][]
        {
            new float[] {1, 0, 0,    0, 0},
            new float[] {0, 1, 0,    0, 0},
            new float[] {0, 0, 1,    0, 0},
            new float[] {0, 0, 0, 0.8f, 0},
            new float[] {0, 0, 0,    0, 1}
        });
    TransformedImage = ApplyColorMatrix(TransformedImage, color_matrix);
    ShowImage();
}

This code simply creates an appropriate ColorMatrix. It then passes it and the current TransformedImage into the ApplyColorMatrix method and stores the result in variable TransformedImage. It finishes by calling the following ShowImage method to display the result.

// Display the transformed image drawn on top of a checkerboard.
private void ShowImage()
{
    int wid = TransformedImage.Width;
    int hgt = TransformedImage.Height;
    Bitmap bm = new Bitmap(wid, hgt);
    using (Graphics gr = Graphics.FromImage(bm))
    {
        // Draw the checkerboard.
        const int step = 20;
        for (int x = 0; x < wid; x += step)
        {
            for (int y = 0; y < hgt; y += step)
            {
                if (((x / step) + (y / step)) % 2 == 0)
                    gr.FillRectangle(Brushes.Red,
                        x, y, step, step);
                else
                    gr.FillRectangle(Brushes.Yellow,
                        x, y, step, step);
            }
        }

        // Draw the image on top.
        gr.DrawImage(TransformedImage, new Point(0, 0));
    }

    picImage.Image = bm;
}

This method creates a new bitmap that is the same size as the TransformedImage. It then uses nested loops to fill the bitmap with a checkerboard.

The code then draws the TransformedImage onto the bitmap and displays the result in the picImage PictureBox. If the pixels’ alpha components are one (255 in a 24- or 32-bit color system), then the image completely covers the checkerboard. If the pixels’ alpha components are less than one, then the checkerboard will show through.

Conclusion

The program includes a lot of other details such as how it loads and saves images, reverts to the original image, and enables and disables menu items when appropriate. Download the example to see additional details and to experiment with the program. If you come up with some particularly interesting images, feel free to post a note below.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in graphics, image processing | Tagged , , , , , , , | Leave a comment

Draw a rounded polygon in C#

[rounded polygon]

This post uses techniques described in my post Connect two line segments with a circular arc in C# to draw a rounded polygon. The earlier post uses mathematics to find a circular arc to join two line segments. This post adapts that method to connect a series of segments that make up a polygon.

Arc Changes

This example makes a couple of changes to the previous example’s circular arc code. First, it moves the earlier arc calculation code into a static Arcs class. The new example calls that class’s methods to find the arcs that it needs to draw a rounded polygon.

The new example also modifies several key methods so they return a Boolean value indicating whether they succeeded. The previous example’s versions of the FindArcWithRadius, FindArcFromSegments, and FindIntersection methods threw exceptions if they could not find arcs or intersections. The new example uses the methods’ Boolean return values to ignore cases where the method cannot find the appropriate arcs.

For example, when you’re drawing a polygon, you might move the mouse back to the polygon’s previous point. The methods cannot find an intersection that involves a line segment with the same start and end point. The new example simply ignores that zero-length segment.

[rounded polygon]

Note that while you are drawing the polygon, the program should not draw a closed polygon. Instead it should draw an open curve as shown in the picture on the right.

RoundedPolyline

The key to the program is the following RoundedPolyline method. This method takes as inputs a list of points that define the polygon (or open curve) and returns a GraphicsPath object that uses line segments and circular arcs to draw the rounded polygon.

// Convert an array of points into a GraphicsPath
// that connects the points with segments joined
// by circular arcs.
private GraphicsPath RoundedPolyline(
    List<PointF> point_list,
    int radius, bool is_closed)
{
    // Remove adjacent duplicates from the list.
    point_list = RemoveDuplicates(point_list);
    int num_points = point_list.Count;
    if (num_points < 2) return null;

    // Convert into an array.
    PointF[] points = point_list.ToArray();

    // segments[i] is the segment from points[i] to points[i + 1];
    SegmentInfo[] segments = new SegmentInfo[num_points];

    // Initially the segments are the polygon's sides.
    for (int i = 0; i < num_points; i++)
    {
        int j = (i + 1) % num_points;
        segments[i] = new SegmentInfo(points[i], points[j]);
    }

    // arcs[i] is the arc at points[i].
    ArcInfo[] arcs = new ArcInfo[num_points];

    // Get arc and segment info between the points.
    for (int i = 0; i < num_points; i++)
    {
        // Find the arc at points[i].
        int j = i - 1;
        if (j < 0) j += num_points;
        PointF s1p1 = points[j];
        PointF s1p2 = points[i];
        PointF s2p1 = points[(i + 1) % num_points];
        PointF s2p2 = points[i];

        RectangleF rect;
        float start_angle, sweep_angle;
        PointF s1_far, s1_close, s2_far, s2_close;

        // Find the arc.
        if (Arcs.FindArcWithRadius(s1p1, s1p2, s2p1, s2p2, radius,
            out rect, out start_angle, out sweep_angle,
            out s1_far, out s1_close, out s2_far, out s2_close))
        {
            // Save the arc info.
            arcs[i] = new ArcInfo(rect, start_angle, sweep_angle);

            // Update the adjacent segment infos.
            j = i - 1;
            if (j < 0) j += num_points;
            segments[j].EndPoint = s1_close;
            segments[i].StartPoint = s2_close;
        }
    }

    // If the path should not be closed,
    // reset the first segment's start point
    // and the second-to-last segment's end point.
    if (!is_closed)
    {
        segments[0].StartPoint = points[0];
        segments[num_points - 2].EndPoint =
            points[num_points - 1];
    }

    // Create the GraphicsPath.
    GraphicsPath path = new GraphicsPath();

    // Add the middle segments and arcs.
    for (int i = 0; i < num_points - 1; i++)
    {
        // Add the arc at points[i].
        if (is_closed || i > 0)
        {
            path.AddArc(arcs[i].Rect,
                arcs[i].StartAngle, arcs[i].SweepAngle);
        }

        // Add the segment between points[i] and points[i + 1];
        path.AddLine(segments[i].StartPoint, segments[i].EndPoint);
    }

    // If the path should be closed, add the final arc and segment.
    if (is_closed)
    {
        // Add the final arc.
        path.AddArc(arcs[num_points - 1].Rect,
            arcs[num_points - 1].StartAngle,
            arcs[num_points - 1].SweepAngle);

        // Add the final segment;
        path.AddLine(
            segments[num_points - 1].StartPoint,
            segments[num_points - 1].EndPoint);

        // Close the path.
        path.CloseFigure();
    }
    return path;
}

The method first uses the following RemoveDuplicates helper method to make a copy of the list of points that contains no adjacent duplicate points. (Because the methods that make circular arcs cannot make an arc with a zero-length segment.)

// Copy a list of points into a new list
// with no adjacent duplicates.
private List RemoveDuplicates(List original_list)
{
    // Make the result list.
    List new_list = new List();

    // Keep track of the last item we added.
    // Initially compare the first item with the last one.
    int num_items = original_list.Count;
    T last_item = original_list[num_items - 1];

    // Loop through the items.
    foreach (T item in original_list)
    {
        // If this is not the same as the previous item, add it.
        if (!item.Equals(last_item))
        {
            new_list.Add(item);
            last_item = item;
        }
    }

    return new_list;
}

This is a generic method, so it can remove adjacent duplicates from a list of any kind of object. The method takes as a parameter a list of generic objects with type T and returns a similar list. It first creates the new list and saves a reference to the last item in the list in variable last_item. It then loops through the original list. If an item is not the same as the previously added item stored in last_item, the code adds it to the new list.

After it removes any duplicate points, the the RoundedPolygon method returns if the list contains fewer than two points. If the list contains at least two points, then the code converts it into an array.

Next the method creates an array of SegmentInfo objects named segments. The SegmentInfo class simply contains StartPoint and EndPoint fields. It’s a straightforward class so I won’t show it here. Download the example to see the details.

The code then loops through the points and creates a SegmentInfo object connecting each point to the one that follow it. For example, segments[i] represents a segment that connects points[i] and points[i + 1]. (Wrapping around to the beginning to connect the last point to the first.)

Now the method creates an array of ArcInfo objects named arcs. The ArcInfo class simply holds a RectangleF to define and arc’s ellipse, its start angle, and its sweep angle. Those are the values that we will later need to pass into GraphicsPath methods to define an arc. Like the SegmentInfo class, this one is straightforward so I won’t show it here. Download the example to see it.

The arcs[i] entry will hold the arc at point i in the polygon. To define the arcs, and to update the segments so they end where the arcs begin, the program loops over the indices of the points that define the polygon.

For each point the code finds the end points of the polygon’s edges that start and end at this point. For example, suppose we are considering point i and that point is is not at the beginning or end of the array of points. Then the edge leading into that point is points[i - 1] --> points[i] and the edge leading out of that point is points[i] --> points[i + 1].

After it finds the points that define the segments adjacent to this point, the method calls the FindArcWithRadius method to find the arc connecting the two segments. If the arc exists, the code saves its information in the corresponding arcs entry. It also updates the end points of the segments adjacent to the arc so they end and begin where the arc begins and ends respectively.

After finding the arcs and updating the segments, the method checks whether the arc should be closed. If the rounded polygon should be closed, then the segments and arcs arrays are finished.

If the rounded polygon should not be closed, then the code resets the starting end point for the first segment so it starts at the first input point. It also updates the second-to-last segment so it ends at the last input point.

[rounded polygon]

The picture on the right shows where the program makes those adjustments. The first point is marked with a red dot. The red line shows where the first segment was updated so it starts at that point.

The last point is marked with a blue dot. The blue line shows where the second-to-last segment was updated so it ends at that point.

After it adjusts the segments if necessary, the method is ready to draw the rounded polygon. It first creates a Graphics method object to hold the rounded polygon. It then loops through the segments and arcs adding them to the path. If the path should be closed or the looping variable i is greater than zero, then the loop adds the corresponding arc to the path. That lets it skip first arc if the path should be open. After adding the arc, the code adds the following segment to the path.

The loop stops before it adds the final arc and segment. If the rounded polygon should be closed, then the code draws them. The code also closes the path’s figure so it knows that the path should be closed.

Finally the method returns the GraphicsPath.

Paint

Whenever you move the mouse, click the mouse, or change the radius text box, the program refreshes its PictureBox and the following Paint event handler redraws the rounded polygon.

private void picCanvas_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.Clear(picCanvas.BackColor);
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

    if (Drawing && (Points.Count >= 2))
    {
        GraphicsPath path =
            RoundedPolyline(Points, Radius, false);
        if (path != null)
        {
            using (Pen pen = new Pen(Color.Red, 3))
            {
                e.Graphics.DrawPath(pen, path);
            }
        }
    }
    else if (!Drawing && (Points.Count >= 3))
    {
        GraphicsPath path =
            RoundedPolyline(Points, Radius, true);
        if (path != null)
        {
            e.Graphics.FillPath(Brushes.LightGreen, path);
            using (Pen pen = new Pen(Color.Green, 3))
            {
                e.Graphics.DrawPath(pen, path);
            }
        }
    }
}

This code first clears the PictureBox with its background color and prepares to draw smoothly.

It then checks the Drawing variable to see if you are currently drawing a rounded polygon. If you are drawing and have already defined at least two points, then the code should draw an open curve. In that case the program calls RoundedPolyline passing false into its is_closed parameter. If the returned path is not null, the program draws it with a thick red pen.

If you are not drawing and you have previously defined at least three points, then a rounded polygon is defined so the program should draw it as a closed curve. In that case the program calls RoundedPolyline passing true into its is_closed parameter. If the returned path is not null, the program fills it in light green and outlines it with a thick green pen.

Conclusion

[rounded polygon]

The program works pretty well, although there are still cases where you can make it produce strange results. If the rounded polygon has two points that are too close together for the given radius value, then the arcs at those points may extend beyond their corresponding segments and stick out as shown in the picture on the right. You can avoid that by ensuring that the points are not too close together for the given radius.

Download the example program to experiment with it and to see additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, drawing, graphics | Tagged , , , , , , , , , | Leave a comment

Use big toolstrip buttons in C#

[ToolStrip]

This example shows how you can make big toolstrip buttons. It also shows how to make buttons act as comboboxes that let you select images.

At design time, I created the toolstrip and added the button with the flower on it. To make it big enough to hold its picture, I set its ImageScaling property to None. This is important. It tells the control not to scale its image to whatever size the control thinks is best. If you don’t do this, then the button scales its image to 16×16 pixels.

The example builds the rest of its tools in code, mostly so it’s easy to describe the steps that you need to take. You could do most if not all of this interactively in the Form Designer if you prefer.

Form_Load

The form’s Load event handler creates the tools. To make it easier to follow the code, I’ll describe the event handler in sections. The following code shows the first section, which creates the diamond buttons that are one level below the toolstrip’s main buttons. When you click the diamond button on the toolstrip, these sub-items are displayed so you can select one.

private void Form1_Load(object sender, EventArgs e)
{
    // Make all of the items. (Just to get it over with.)
    Image[] diamond_images =
    {
        Properties.Resources.diamond1,
        Properties.Resources.diamond2,
        Properties.Resources.diamond3,
    };
    string[] diamond_tips =
    {
        "Diamond 1",
        "Diamond 2",
        "Diamond 3",
    };
    int num_diamonds = diamond_images.Length;
    ToolStripButton[] diamond_btns = new ToolStripButton[num_diamonds];
    for (int i = 0; i < num_diamonds; i++)
    {
        ToolStripButton btn = new ToolStripButton(diamond_images[i]);
        btn.ImageScaling = ToolStripItemImageScaling.None;
        btn.ToolTipText = diamond_tips[i];
        btn.Tag = i;
        btn.Click += dropdownButton_Click;
        diamond_btns[i] = btn;
    }

At design time, I added the diamond and smiley images to the program’s properties. This code puts the diamond images in an array so it can loop through them. It also puts the images’ corresponding tooltip text an array.

The code then loops through the images and tooltip values. For each pair of values it creates a ToolStripButton that displays the image.

It then sets the button’s ImageScaling property to None.

Next the code sets the button’s ToolTipText and Tag properties. The Tag property stores the index of the entry.

The code sets the button’s Click event handler to a method that I’ll describe later. Finally, the code saves the button in the diamond_btns array.

The program then repeats those steps to make the smiley face buttons. That code is basically the same as the previous code, so I won’t show it here. Download the example to see the details.

The following code shows the rest of the form’s Load event handler.

// Prepare the ToolStrip.
    toolStrip1.BackColor = Color.LightBlue;

    // Do the following to make all buttons have
    // the same desired image size.
    //toolStrip1.ImageScalingSize = new Size(50, 50);

    // Make the diamond dropdown button.
    DiamondButton = new ToolStripDropDownButton();
    toolStrip1.Items.Add(DiamondButton);
    DiamondButton.DropDownItems.AddRange(diamond_btns);
    DiamondButton.ImageScaling = ToolStripItemImageScaling.None;
    SelectItem(DiamondButton, diamond_btns[0]);

    // Make the smiley dropdown button.
    SmileyButton = new ToolStripDropDownButton();
    toolStrip1.Items.Add(SmileyButton);
    SmileyButton.DropDownItems.AddRange(smiley_btns);
    SmileyButton.ImageScaling = ToolStripItemImageScaling.None;
    SelectItem(SmileyButton, smiley_btns[0]);

    // Move the top of the Panel control below the ToolStrip.
    panel1.Top = toolStrip1.Bottom;
}

Now the code prepares the toolstrip and the ToolStripDropDownButton objects that it contains directly. The code first sets the toolstrip’s background color so it’s easy to see where the toolstrip is on the form.

If you want the toolstrip to scale all of the images that it contains to the same size, you can set that size with the ImageScalingSize property. Then if you leave a button’s ImageScaling property set to SizeToFit, the toolstrip resizes that button’s image to this size. I have commented out the ImageScalingSize statement in this code.

Next the code creates a ToolStripDropDownButton named DiamondButton and adds the new button to the toolstrip. It uses the button’s DropDownItems.AddRange method to add all of the diamond ToolStripButton objects that were created earlier to the dropdown button’s list. (This is why I placed the buttons in the diamond_btns array.)

The code then sets the dropdown button’s ImageScaling property to None so the button is not scaled even if the toolstrip’s ImageScalingSize property is defined. For example, you could use the toolstrip’s ImageScalingSize property to set a default size for the images that it contains and then have some dropdown buttons set ImageScaling to None so they don’t use the default scale.

The code finishes this button by calling the SelectItem method describe shortly to make the dropdown button show its first selection.

Next the program repeats those steps to create the smiley dropdown button.

The form’s Load event handler finishes by setting the panel1 control’s Top property equal to the toolstrip’s Bottom value. All of the form’s other controls are arranged inside the panel1 control. This makes the panel fit nicely up against the toolstrip. If you don’t do this, then the panel keeps its original position on the form. Depending on the toolstrip’s height, that might make the panel overlap the toolstrip or it might create a gap between the two. (In this example, it doesn’t matter because I created the flower button at design time and that button determines the toolstrip’s height.)

SelectItem

The following SelectItem method makes a dropdown button display the image stored in one of its button subitems.

private void SelectItem(
    ToolStripDropDownButton parent,
    ToolStripButton item)
{
    parent.Image = item.Image;
    parent.ToolTipText = item.ToolTipText;
    parent.Tag = item.Tag;
}

This method makes the parent ToolStripDropDownButton use the same Image, ToolTipText, and Tag values as the indicated ToolStripButton.

dropdownButton_Click

The following code shows the dropdownButton_Click event handler that is called when you select a ToolStripButton.

private void dropdownButton_Click(object sender, EventArgs e)
{
    ToolStripButton btn = sender as ToolStripButton;
    ToolStripDropDownButton parent =
        btn.OwnerItem as ToolStripDropDownButton;
    SelectItem(parent, btn);
}

This code first converts the sender parameter into the ToolStripButton that you clicked to raise the event. That object’s OwnerItem property is a reference to the toolstrip item that holds the button. In this example, that item is the ToolStripDropDownButton that has the button as a sub-item. The program converts that value into a ToolStripDropDownButton object named parent.

The code then calls the SelectItem method, passing it the parent and button, to make the parent display the button’s image.

Verifying the Selection

When you click the example program’s Check Values button, the following code executes.

private void btnCheckValues_Click(object sender, EventArgs e)
{
    txtDiamond.Text = DiamondButton.ToolTipText;
    txtSmiley.Text = SmileyButton.ToolTipText;
}

This code simply displays the ToolStripDropDownButton objects’ current tooltip text values so you can see that they are keeping track of the items that you have selected.

Other parts of your code can use those values or the object’s Tag properties to determine which sub-items are currently selected.

Conclusion

You can use two key properties to change the sizes of buttons on a toolstrip.

If you set the toolstrip’s ImageScalingSize property to a size, then it will try to scale any buttons that it contains to that size. That includes buttons contained in dropdown menus.

If you set a button’s ImageScaling property to None, then the button will make its image its normal, unscaled size even if the toolstrip has an ImageScalingSize defined.

Getting the hang of those properties takes some practice. Download the example program and experiment with it. Once you get used to the properties, you can use the program’s SelectItem method to create big toolstrip buttons and use dropdowns holding images as if they were comboboxes relatively easily.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in controls, tools, user interface | Tagged , , , , , , , , , | Leave a comment

Rotate images to straighten them in C#

[rotate images]

I’ve written a couple of examples that show how to rotate images. I made many of them because I wanted to adjust a picture I had taken that was slightly crooked. Those examples work well, but they can be fairly slow if the image is large so they’re hard to use to make small adjustments.

This example lets you rotate images by drawing a horizon line on the image to indicate where horizontal should be. The program them rotates the image to make that line horizontal.

To make it slightly easier to align objects in the picture, the program allows you to draw a grid on top of the image if you like.

The example performs three main tasks: line selection, image rotation, and drawing.

Line Selection

The program uses the following code to let you draw the horizon line on the image.

private float Angle = 0;
private Point StartPoint = new Point(50, 100);
private Point EndPoint = new Point(210, 100);
private bool Drawing = false;

// Let the use select an orientation line.
private void picImage_MouseDown(object sender, MouseEventArgs e)
{
    StartPoint = e.Location;
    EndPoint = e.Location;
    Drawing = true;
}

private void picImage_MouseMove(object sender, MouseEventArgs e)
{
    if (!Drawing) return;
    EndPoint = e.Location;
    picImage.Refresh();
}

private void picImage_MouseUp(object sender, MouseEventArgs e)
{
    Drawing = false;

    // Calculate the line's angle.
    int dx = EndPoint.X - StartPoint.X;
    int dy = EndPoint.Y - StartPoint.Y;
    double new_angle = Math.Atan2(dy, dx) * 180.0 / Math.PI;
    
    // Subtract this angle from the total angle so far.
    Angle -= (float)new_angle;

    // Make the new rotated image.
    DrawImage();
}

This is relatively straightforward code for drawing a line. Variable Angle stores the pictures current angle of rotation. The StartPoint and EndPoint variables keep track of the mouse’s position while you are drawing. The Drawing variable is true while you are drawing.

The MouseDown event handler starts the process. It saves the mouse’s location in the two point variables and sets Drawing = true.

The MouseMove event handler does nothing if Drawing is false. Otherwise it updates the position of EndPoint and refreshes the program’s PictureBox to draw the new line. The Paint event handler shown shortly draws the line.

The MouseUp event handler finishes the line. It sets Drawing = false to indicate that drawing is over.

The program then calculates the differences in X and Y coordinates between the line’s start and end points. It uses those differences to calculate the line’s angle. The Math.Atan2 method returns an angle in radians, so the code converts the angle into degrees.

The code then subtracts the line’s angle from the current value of Angle. That rotates the new horizon line so it is horizontal. The event MouseUp handler finishes by calling the DrawImage method to rotate the image that you have loaded.

Image Rotation

The most interesting part of the program is the following DrawImage method.

// Display the image rotated by the current amount.
private void DrawImage()
{
    if (OriginalBitmap == null)
    {
        picImage.Refresh();
        return;
    }

    // Get the image's dimensions.
    int wid = OriginalBitmap.Width;
    int hgt = OriginalBitmap.Height;

    // Make a Matrix representing the rotation.
    Matrix matrix = new Matrix();
    matrix.Rotate(Angle);

    // Rotate the image's corners to see
    // how large the rotated image must be.
    PointF[] points =
    {
        new PointF(0, 0),
        new PointF(wid, 0),
        new PointF(0, hgt),
        new PointF(wid, hgt),
    };
    matrix.TransformPoints(points);

    // Get the rotated bounds.
    float xmin = points[0].X;
    float xmax = xmin;
    float ymin = points[0].Y;
    float ymax = ymin;
    for (int i = 1; i < points.Length; i++)
    {
        if (xmin > points[i].X) xmin = points[i].X;
        if (xmax < points[i].X) xmax = points[i].X;
        if (ymin > points[i].Y) ymin = points[i].Y;
        if (ymax < points[i].Y) ymax = points[i].Y;
    }

    // Get the new image's dimensions.
    float new_wid = xmax - xmin;
    float new_hgt = ymax - ymin;

    // Add a translation to move the rotated image
    // to the center of the new bitmap.
    matrix.Translate(new_wid / 2, new_hgt / 2, MatrixOrder.Append);

    // Make the new bitmap.
    RotatedBitmap = new Bitmap((int)new_wid, (int)new_hgt);
    using (Graphics gr = Graphics.FromImage(RotatedBitmap))
    {
        gr.InterpolationMode = InterpolationMode.High;
        gr.Clear(Color.White);
        gr.Transform = matrix;

        // Draw the image centered at the origin.
        PointF[] dest_points =
        {
            new PointF(-wid / 2, -hgt / 2),
            new PointF(wid / 2, -hgt / 2),
            new PointF(-wid / 2, hgt / 2),
        };
        gr.DrawImage(OriginalBitmap, dest_points);
    }

    // Display the result.
    picImage.Image = RotatedBitmap;
}

If you have not yet loaded an image, then the form-level variable OriginalBitmap is null. In that case, the method simply returns without doing anything.

If OriginalImage is not null, the program needs to figure out how large the rotated image will be. To do that, it gets the image’s dimensions. It then makes a Matrix representing a rotation through the angle Angle. It uses the dimensions to make points representing the image’s corners and puts them in an array. The program then uses the Matrix to rotate the points and loops through them to find their minimum and maximum X and Y coordinates. From those values it calculates the dimensions of the rotated image.

So far the Matrix represents a rotation. The code adds a translation to move the origin (0, 0) so it is centered over the image when placed so it’s upper left corner is at the origin.

The program then makes a new bitmap with the required size and creates an associated Graphics object. It sets the Graphics object’s Transform property to the Matrix so any drawing the object does is automatically rotated and translated.

Next the program draws the original image that you loaded centered at the origin. The Graphics object automatically rotates and translates it so it is centered on the new bitmap.

The method finishes by displaying the new bitmap in the picImage control.

Drawing

The following Paint event handler draws the horizon line and alignment grid.

// Draw the horizon line and alignment grid.
private void picImage_Paint(object sender, PaintEventArgs e)
{
    if (Drawing)
    {
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        e.Graphics.DrawLine(Pens.Yellow, StartPoint, EndPoint);
        using (Pen pen = new Pen(Color.Red))
        {
            pen.DashPattern = new float[] { 5, 5 };
            e.Graphics.DrawLine(pen, StartPoint, EndPoint);
        }
    }

    if (mnuFormatDrawGrid.Checked)
    {
        const int dx = 50;
        int xmax = picImage.ClientSize.Width;
        int ymax = picImage.ClientSize.Height;
        for (int x = 0; x < xmax; x += dx)
            e.Graphics.DrawLine(Pens.Silver, x, 0, x, ymax);
        for (int y = 0; y < ymax; y += dx)
            e.Graphics.DrawLine(Pens.Silver, 0, y, xmax, y);
    }
}

The program only draws the horizon line while you are drawing it, so the code checks the Drawing variable to see if you are drawing that line. If Drawing is true, the code draws a line between StartPoint and EndPoint in yellow. It then creates a dashed red pen and draws the line again. The result is a dashed red and yellow line that’s fairly easy to see no matter what color the image is behind the line.

The Format menu’s Draw Grid item has its CheckOnClick property set to true, so that item checks and unchecks itself automatically when you select it at run time. The Paint event handler checks the menu item’s Checked property to see if it is currently checked. If the menu item is checked, the program uses two loops to draw vertical and horizontal lines on the picture.

Recall that the DrawImage method set the picImage control’s Image property to the rotated bitmap. When the Paint event runs, the control’s image has been reset to show the bitmap. When Paint draws the horizon line and the grid, those are automatically drawn on top of the image.

Conclusion

Those are the example’s most interesting pieces, but there are still some extra details such as how the program loads and saves images. Download the example to see those details.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, graphics, image processing, transformations | Tagged , , , , , , , , , | Leave a comment

Make a hexagonal montage of pictures in C#

[hexagonal montage]

This example combines techniques from several earlier posts to make a hexagonal montage. It uses the techniques demonstrated in the post Draw a hexagonal grid in C# to define the grid and to make positions on the form two and from grid rows and columns. It uses clipping techniques used by the post Clip an image to a polygon in C# to clip images to the hexagons. Finally it uses the general approach demonstrated by the post Make an improved diagonal picture montage in C# to make the hexagonal montage.

This is a fairly long example so I won’t describe all of the techniques described in those earlier posts. See those posts for the details.

This example defines a static Hex class to contain methods that work with hexagons. Those methods include methods that convert points to and from locations in the grid, draw the grid, get points defining a hexagon, and draw pictures inside a hexagon.

However I will briefly explain what happens when you click on the program’s picGrid PictureBox.

Selecting Images

The program defines the following Hexagon class to keep track of the image that should be drawn in a particular hexagon.

public class Hexagon
{
    public int Row, Column;
    public Bitmap Picture;
    public string FileName;
    public Hexagon(int row, int column, Bitmap picture, string file_name)
    {
        Row = row;
        Column = column;
        Picture = picture;

        // Remove the file path.
        FileInfo file_info = new FileInfo(file_name);
        FileName = file_info.Name;
    }
}

When you click on the picGrid PictureBox, the following code executes.

// Add the clicked hexagon to the Hexagons list.
private void picGrid_MouseClick(object sender, MouseEventArgs e)
{
    // Get the row and column clicked.
    int row, col;
    Hex.PointToHex(e.X, e.Y, HexHeight, out row, out col);

    // Remove any existing record for this cell.
    RemoveHexagon(row, col);

    // Let the user select a new picture.
    if (ofdFile.ShowDialog() == DialogResult.OK)
    {
        Bitmap bm = LoadBitmapUnlocked(ofdFile.FileName);
        Hexagons.Add(new Hexagon(row, col, bm, ofdFile.FileName));
    }

    // Redraw.
    picGrid.Refresh();
}

This event handler uses the Hex class’s static PointToHex method to find the row and column of the point that you clicked in the hexagonal grid. It then calls the RemoveHexagon method described shortly to remove any existing hexagon that is currently at that position.

Next the code displays an OpenFileDialog to let you select an image file. If you pick a file, the code uses it to create a new Hexagon object for the selected row and column and containing the image that you selected. The code finishes by refreshing the PictureBox to redraw the grid.

The following code shows the RemoveHexagon method.

// Remove the Hexagon at this position if there is one.
private void RemoveHexagon(int row, int col)
{
    int index = FindHexagon(row,col);
    if (index >= 0) Hexagons.RemoveAt(index);
}

The RemoveHexagon method calls the following FindHexagon method to find the index of the hexagon at the clicked row and column, if there is already a hexagon there. If the method finds a hexagon at that position, the RemoveHexagon method removes it from the Hexagons list.

// Find the Hexagon at this position if there is one.
private int FindHexagon(int row, int col)
{
    for (int i = 0; i < Hexagons.Count; i++)
    {
        if ((Hexagons[i].Row == row) &&
            (Hexagons[i].Column == col))
                return i;
    }
    return -1;
}

This method simply loops through the objects in the Hexagons list and returns the index of any object that has the desired row and column. If it finds no such object, the method returns -1.

Drawing the Grid

The picGrid PictureBox control’s Paint event handler calls the following DrawGrid method to draw the grid.

private void DrawGrid(Graphics gr, int xmax, int ymax)
{
    gr.Clear(picBackgroundColor.BackColor);
    gr.SmoothingMode = SmoothingMode.AntiAlias;

    // Draw the selected hexagons.
    float xmin = BorderThickness / 2f;
    foreach (Hexagon hexagon in Hexagons)
    {
        PointF[] points = Hex.HexToPoints(HexHeight,
            hexagon.Row, hexagon.Column, xmin, xmin);

        if (points[3].X > xmax) continue;
        if (points[4].Y > ymax) continue;

        Hex.DrawImageInPolygon(gr,
            Hex.HexToPoints(HexHeight,
                hexagon.Row, hexagon.Column, xmin, xmin),
                hexagon.Picture);
    }

    // Draw the grid.
    using (Pen pen = new Pen(picBorderColor.BackColor, BorderThickness))
    {
        Hex.DrawHexGrid(gr, pen,
            xmin, xmax, xmin, ymax, HexHeight);
    }
}

[hexagonal montage]

This code loops through the Hexagon objects in the Hexagons list. For each object, it uses the Hex class’s HexToPoints method to get points that define the object’s hexagon. The HexToPoints method returns the points in the order shown in the picture on the right, so the code looks at the points with indices 3 and 4 to determine whether the hexagon lies partly outside of the PictureBox. If the hexagon does not fit, the code skips it.

If the hexagon fits, the code calls the Hex.DrawImageInPolygon method to draw the hexagon’s image.

After it has drawn all of the images, the code calls the Hex.DrawHexGrid method to draw the hexagonal outlines.

Loading Many Files

The program includes one other interesting technique that I want to discuss. If you open the File menu and select Open Files in Directory, then the program executes the following code.

// Load the files from a directory.
private void mnuFileOpenDirectoryFiles_Click(object sender, EventArgs e)
{
    if (ofdDirectoryFiles.ShowDialog() == DialogResult.OK)
    {
        // Get a list of the files in the directory.
        FileInfo info = new FileInfo(ofdDirectoryFiles.FileName);
        DirectoryInfo dir_info = info.Directory;
        List<FileInfo> file_infos = new List<FileInfo>();
        foreach (FileInfo file_info in dir_info.GetFiles())
        {
            string ext = file_info.Extension.ToLower().Replace(".", "");
            if ((ext == "bmp") || (ext == "png") ||
                (ext == "jpg") || (ext == "jpeg") ||
                (ext == "gif") || (ext == "tiff"))
            {
                file_infos.Add(file_info);
            }
        }

        // Calculate the number of rows and columns.
        int num_rows = (int)Math.Sqrt(file_infos.Count);
        int num_cols = num_rows;
        if (num_rows * num_cols < file_infos.Count)
            num_cols++;
        if (num_rows * num_cols < file_infos.Count)
            num_rows++;

        // Load the files.
        Hexagons = new List<Hexagon>();
        int index = 0;
        for (int row = 0; row < num_rows; row++)
        {
            for (int col = 0; col < num_cols; col++)
            {
                string name = file_infos[index].Name;
                string full_name = file_infos[index].FullName;
                Bitmap bm = LoadBitmapUnlocked(full_name);
                Hexagons.Add(new Hexagon(row, col, bm, name));

                index++;
                if (index >= file_infos.Count) break;
            }
            if (index >= file_infos.Count) break;
        }

        picGrid.Refresh();
    }
}

This code displays an OpenFileDialog to let you select a file in a directory that contains images. If you select a file, the code gets a DirectoryInfo object representing the file’s directory. It calls that object’s GetFiles method to loop through the file in the directory and adds those that are image files to the file_infos list.

Next the code calculates a reasonable number of rows and columns to hold the files’ images. It starts by making the number of rows and columns equal to the square root of the number of images truncated to an integer. If that doesn’t give enough room, the method adds an extra column. If that still doesn’t give enough room, the method adds another row.

The code then loops through the rows and columns. For each position in the grid, the program loads the next image file from the file_infos list and creates a Hexagon object for that position.

After it finishes loading all of the files into Hexagon objects, the code refreshes the picGrid PictureBox to show the new grid.

Conclusion

That’s about all there is to the example that’s new. There are lots of other details such as how the program loads images without locking their files, how the program saves the resulting montage, how the Hex class calculates the positions of grid cells, and how the program displays the name of the image file under mouse. See the previous examples and download this example to see those details.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in drawing, graphics, image processing, tools | Tagged , , , , , , , , , , , , , , | 1 Comment

Find drawn characters under the mouse in C#

[drawn characters]

The following examples find the positions of drawn characters in a string that is drawn by using the Graphics object’s DrawString method.

This example does the opposite: it finds the drawn characters at given positions.

The program uses the following variables to keep track of the text and the positions of the drawn characters.

// The text.
private string TheText =
    "When in the course of human events it " +
    "becomes necessary for the quick brown " +
    "fox to jump over the lazy dog...";

// The character locations.
private List<RectangleF> TheRects;

When it starts, the program uses the following code to draw its text.

// Draw the text.
private void Form1_Load(object sender, EventArgs e)
{
    // Make a Bitmap to hold the text.
    Bitmap bm = new Bitmap(
        picText.ClientSize.Width,
        picText.ClientSize.Height);
    using (Graphics gr = Graphics.FromImage(bm))
    {
        gr.Clear(Color.White);

        // Don't use TextRenderingHint.AntiAliasGridFit.
        gr.TextRenderingHint = TextRenderingHint.AntiAlias;

        // Make a font to use.
        using (Font font = new Font("Times New Roman", 16, FontStyle.Regular))
        {
            // Draw the text.
            gr.DrawString(TheText, font, Brushes.Blue, 4, 4);

            // Measure the characters.
            TheRects = MeasureCharacters(gr, font, TheText);
        }
    }

    // Display the result.
    picText.Image = bm;
}

This code creates a bitmap and draws the text on it. It also calls the MeasureCharacters method described in the post Measure character positions when drawing long strings in C# to find the locations of the drawn characters. It finishes by displaying the bitmap on the picText PictureBox control

When the mouse moves over the picText control, the following event handler executes.

private void picText_MouseMove(object sender, MouseEventArgs e)
{
    // See if the mouse is over one of the character rectangles.
    string new_text = "";
    for (int i = 0; i < TheText.Length; i++)
    {
        if (TheRects[i].Contains(e.Location))
        {
            new_text =
                "Character " + i.ToString() + ": " + TheText[i];
            break;
        }
    }
    if (lblChar.Text != new_text) lblChar.Text = new_text;
}

This code loops through the TheRects list checking the rectangles that contain the drawn characters to see if any contains the mouse’s position. If the mouse position lies inside one of the rectangles, the program displays the index and value of the corresponding character.

See the earlier posts and download the example to see additional details.

Unfortunately this method doesn’t work for strings drawn in more complex ways. For example, if you use a formatting rectangle and a StringFormat object to draw wrapped text with ellipses, then the string measuring methods won’t work. In that case you might want to display the text in a TextBox or RichTextBox control and use a TextRenderer object to measure character positions.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, drawing, fonts, graphics, strings | Tagged , , , , , , , , , , , , , , , , | Leave a comment