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

Clip an image to a polygon in C#

[clip an image]

This example lets you select a polygon and then uses it to clip an image to it. It uses the technique described in my previous post Build a polygon selector class in C# to let you select the polygon.

The following section describes how the program lets you select a polygon. The section after that explains how the program clips images.

Selecting Polygons

When the program starts, the following code prepares a PolygonSelector object to let you select polygons.

// The polygon selector.
private PolygonSelector Selector;
private PointF[] Polygon = null;

// Prepare the selector.
private void Form1_Load(object sender, EventArgs e)
{
    Selector = new PolygonSelector(picCanvas, new Pen(Color.Red, 3));
    Selector.PolygonSelected += Selector_PolygonSelected;
}

The field Selector holds a references to a PolygonSelector. The Polygon array will hold the points that define the polygon.

The form’s Load event handler creates the PolygonSelector and registers the Selector_PolygonSelected method to catch the selector’s PolygonSelected event.

The selector is attached to the picCanvas control. When you press the mouse down on that control, the selector automatically takes over and lets you select a polygon. See the previous post for an explanation of how the selector works.

When you finish selecting a polygon, the selector raises its PolygonSelected event and the following event handler catches it.

// The user has selected a polygon. Save it.
void Selector_PolygonSelected(object sender, PolygonEventArgs args)
{
    Polygon = PointsToPointFs(args.Points);
    picCanvas.Refresh();
}

This code gets the polygon’s points from the event handler’s e.Points list. It calls the PointsToPointFs method to convert the Point values into an array of PointF values, saves the result in the Polygon field, and refreshes the picCanvas PictureBox to show the new image.

The following code shows the PointsToPointFs method.

// Convert Point data into a PointF array.
private PointF[] PointsToPointFs(IEnumerable<Point> points)
{
    var query = from Point point in points select (PointF)point;
    return query.ToArray();
}

This method uses LINQ to convert an IEnumerable<Point> into a PointF array. The method creates a query that iterates over the values in the points enumerable and casts the values into PointF values. The method then calls the query’s ToArray method and returns the result.

The code saves the PointF array in the Polygon field and refreshes the picCanvas PictureBox to show the image clipped in the polygon.

Drawing the Image

The following code shows the Paint event handler that draws the clipped image.

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

    if (Polygon != null)
    {
        DrawImageInPolygon(e.Graphics,
            Polygon, Properties.Resources.Smiley_with_bg);
        using (Pen pen = new Pen(Color.Green, 3))
        {
            e.Graphics.DrawPolygon(pen, Polygon.ToArray());
        }
    }
}

This code sets the Graphics object’s SmoothingMode and clears the picture. If the Polygon field is not null, the code calls the DrawImageInPolygon method described shortly to clip an image to the polygon and draw it. It finishes by creating a thick, green pen and using the pen to draw the polygon.

The following code shows the DrawImageInPolygon method.

// Draw an image so it fills the polygon.
public static void DrawImageInPolygon(Graphics gr,
    PointF[] points, Image image)
{
    // Get the polygon's bounds and center.
    float xmin, xmax, ymin, ymax;
    GetPolygonBounds(points, out xmin, out xmax, out ymin, out ymax);
    float wid = xmax - xmin;
    float hgt = ymax - ymin;
    float cx = (xmin + xmax) / 2f;
    float cy = (ymin + ymax) / 2f;

    // Calculate the scale needed to make
    // the image fill the polygon's bounds.
    float xscale = wid / image.Width;
    float yscale = hgt / image.Height;
    float scale = Math.Max(xscale, yscale);

    // Calculate the image's scaled size.
    float width = image.Width * scale;
    float height = image.Height * scale;
    float rx = width / 2f;
    float ry = height / 2f;

    // Find the source rectangle and destination points.
    RectangleF src_rect = new RectangleF(0, 0,
        image.Width, image.Height);
    PointF[] dest_points =
    {
        new PointF(cx - rx,  cy - ry),
        new PointF(cx + rx,  cy - ry),
        new PointF(cx - rx,  cy + ry),
    };

    // Clip the drawing area to the polygon and draw the image.
    GraphicsPath path = new GraphicsPath();
    path.AddPolygon(points);
    GraphicsState state = gr.Save();
    gr.SetClip(path);   // Comment out to not clip.
    gr.DrawImage(image, dest_points, src_rect, GraphicsUnit.Pixel);
    gr.Restore(state);
}

This code first calls the GetPolygonBounds method to find the largest and smallest X and Y coordinates used by the polygon’s points. That method simply loops through the array’s points and keeps track of the largest and smallest values. It’s relatively straightforward so it isn’t shown here.

The DrawImageInPolygon method then calculates the width and height of the points’ bounds, and finds the center of those bounds.

The method then calculates a scale factor that it should use to make the image fill the points’ bounds. First it calculates the amounts by which it should scale the image to make it fill the bounds horizontally and vertically. It then uses the larger of the two scales to ensure that the image completely fills the bounds. The code uses the scale factor to calculate the image’s scaled size.

Next the code creates a Rectangle that defines the image’s entire area. It also creates an array of PointF values that indicate where the image should be drawn. That area is the same as the polygon’s bounds.

The code then creates a GraphicsPath object and uses its AddPolygon method to add the polygon’s points to it. The code saves the Graphics object’s state and calls its SetClip method to clip future drawing to the path. The method draws the image, now clipped to the polygon, and then restores the Graphics object’s state to remove the clipping. (That isn’t necessary in this example but would be if the program performed additional drawing after drawing the clipped polygon.)

Summary

The key to this example is the Graphics object’s SetClip method, which restricts the object’s drawing to a GraphicsPath. This example sets the path equal to the polygon that you selected. For other programs, you could make the path much more complicated so it contained rectangles, ellipses, and other shapes.

After you set the Graphics object’s clipping region, you simply draw as usual.

See the previous post to see how the PolygonSelector works. Download this example to experiment with it and to see additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Build a polygon selector class in C#

[polygon selector]

This example shows how to make a polygon selector class that makes it easy to let the user select a polygon. I call this kind of class that provides a service for another class a symbiont. In a symbiotic relationship, the smaller of the organisms is called the symbiont and the larger is called the host.

The goal is to make the symbiont do ass much of the work as possible so the host can use it easily. In this example, the polygon selector installs event handlers on the host to let it handle the mouse events that the user produces to select the polygon.

PolygonEventArgs

When the user finishes selecting a polygon, the selector raises a PolygonSelected event. It passes that event the new polygon’s points through an object from the following PolygonEventArgs class.

public class PolygonEventArgs
{
    public List<Point> Points;
    public PolygonEventArgs(List<Point> points)
    {
        Points = points;
    }
}

This class simply holds a public list of the points that make up the polygon. The class’s constructor initializes that list.

PolygonSelector

The PolygonSelector class is relatively simple. However, it’s rather long so I’ll describe it in pieces.

The following code shows the the first part of the polygon selector class.

public class PolygonSelector
{
    // The control on which the polygon will be selected.
    private Control Host;

    // The pen used while selecting the polygon.
    private Pen PolygonPen;

    // The points in the polygon.
    private List<Point> PolygonPoints;

    public PolygonSelector(Control host, Pen pen)
    {
        Host = host;
        PolygonPen = pen;

        // Install a MouseDown event handler.
        Host.MouseDown += Host_MouseDown;
    }

The class stores a reference to the host control in its Host field. The field has type Control so the host could be any type of control. In practice, however, it is intended to be a form or PictureBox.

The PolygonPen field holds the pen that the polygon selector should use while selecting a new polygon. The PolygonPoints list holds the selected points.

The class’s constructor first saves the host and pen. It then installs the following event handler to catch the host’s MouseDown events.

// On left mouse down, start selecting a polygon.
private void Host_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button != MouseButtons.Left) return;

    // Uninstall picCanvas_MouseDown.
    Host.MouseDown -= Host_MouseDown;

    // Add the first point and a copy to be the last point.
    PolygonPoints = new List<Point>();
    PolygonPoints.Add(e.Location);
    PolygonPoints.Add(e.Location);

    // Install an event handler to catch clicks.
    Host.Paint += Host_Paint;
    Host.MouseMove += Host_MouseMove;
    Host.MouseClick += Host_MouseClick;
}

If the user has not pressed the left mouse button, the event handler exits.

If the user did press the left mouse button, the code uninstalls the MouseDown event handler so it does not execute while the user is selecting this polygon. It then creates a new PolygonPoints list and adds the mouse’s location to the list twice. The first instance is the first point in the polygon. The second instance is the initial last point in the polygon. That point will be updated when the mouse moves.

Next the code installs Paint, MouseMove, and MouseClick event handlers on the host. The following code shows the Paint event handler.

// Draw the polygon so far.
private void Host_Paint(object sender, PaintEventArgs e)
{
    if (PolygonPoints.Count > 1)
    {
        e.Graphics.DrawLines(PolygonPen,
            PolygonPoints.ToArray());
    }
}

If there are at least two points in the PolygonPoints list, this code draws the lines that they define.

Note that the host will probably also have a Paint event handler installed. The polygon selector installs its Paint event handler when drawing starts, so will be installed after any others (barring any weird programming shenanigans). That means it will execute last so the new polygon will be drawn on top of anything else that is drawn on the host.

When the user moves the mouse, the following event handler executes.

// Update the last point's position and redraw.
private void Host_MouseMove(object sender, MouseEventArgs e)
{
    PolygonPoints[PolygonPoints.Count - 1] = e.Location;
    Host.Refresh();
}

This code moves the last point in the PolygonPoints list to the mouse’s current position. It then refreshes the host to raise its Paint event. The host’s Paint event handler executes followed by the polygon selector’s event handler.

When the user clicks the mouse, the following event handler executes.

// Add a point to the polygon or end the polygon.
private void Host_MouseClick(object sender, MouseEventArgs e)
{
    int num_points = PolygonPoints.Count;

    // See which button was clicked.
    if (e.Button == MouseButtons.Right)
    {
        // Right button. End the polygon.
        // Remove the last point.
        PolygonPoints.RemoveAt(num_points - 1);

        // Uninstall our event handlers.
        Host.Paint -= Host_Paint;
        Host.MouseMove -= Host_MouseMove;
        Host.MouseClick -= Host_MouseClick;

        // Raise the PolygonSelected event.
        OnPolygonSelected();

        // Reinstall the MouseDown event handler.
        Host.MouseDown += Host_MouseDown;
    }
    else
    {
        // Make the last point permanent.
        // If the last point is different from the
        // one before, add a new last point.
        if (PolygonPoints[num_points - 1] != PolygonPoints[num_points - 2])
        {
            PolygonPoints.Add(e.Location);
            Host.Refresh();
        }
    }
}

This code performs two different tasks depending on whether the user clicked the left or right mouse button. If the user clicked the right button, this code finishes the current polygon. If the user clicked the left button, the code adds a new point to the polygon.

To finish the current polygon, the code removes the final point from the points list. It then uninstalls the polygon selector’s Paint, MouseMove, and MouseClick event handlers. It calls the OnPolygonSelected method described shortly to raise the PolygonSelected event. It finishes by reinstalling the MouseDown event handler so it can start a new polygon.

If the user clicked the left mouse button, the event handler tries to add a new point to the points list. If the last two points are the same, then the code does nothing and the current last point in the list remains the last point. This prevents the polygon selector from adding duplicate points to the polygon.

If the last two points are different, then the code adds the mouse’s current location to the end of the list. That point should be the same as the previous last point as updated by the MouseMove event handler. Future MouseMove events will update this new point’s position.

After it has added the new last point, the code refreshes the host to draw the updated polygon.

The following code shows how the polygon selector raises its PolygonSelected event.

// Event to raise when a polygon is selected.
public delegate void PolygonSelectedEventHandler(
    object sender, PolygonEventArgs args);
public event PolygonSelectedEventHandler PolygonSelected;

// Raise the event.
protected virtual void OnPolygonSelected()
{
    if ((PolygonSelected == null) ||
        (PolygonPoints.Count < 3))
    {
        Host.Refresh();
    }
    else
    {
        PolygonEventArgs args =
            new PolygonEventArgs(PolygonPoints);
        PolygonSelected(this, args);
    }
}

This code first declares a delegate type named PolygonSelectedEventHandler that is a void method that takes as parameters an object and a PolygonEventArgs. It then declares the PolygonSelected event to be of that delegate type.

The OnPolygonSelected method raises the event. It first checks whether the PolygonSelected event is null. This is C#’s goofy way of determining whether any event handlers are registered to catch the event. If no event handlers are registered, or if the polygon does not contain at least three points, then the code simply refreshes the host to redraw without the new partially selected polygon.

If there are event handlers ready to catch the event, presumably in the host, and if the polygon contains at least three points, then the code creates a new PolygonEventArgs object holding the new polygon’s points. It then invokes the event handler passing it the current polygon selector object and the PolygonEventArgs.

Disabling the Polygon Selector

There’s one odd potential problem with the polygon selector class. Suppose the user selects a new tool or something on the program’s form and the host needs to disable the polygon selector. The obvious thing to do is to release any references to the polygon selector object so it would go away and stop working. Unfortunately that object would probably keep its Host_MouseDown event handler installed. If the user later pressed the mouse down, it would start selecting a new polygon.

Eventually the garbage collector would run and kill the polygon selector, but you don’t know when that might happen.

What we need is a way to explicitly make the polygon selector stop. The IDisposable interface seems like a good approach. However, that interface is intended to free unmanaged resources when an object is being destroyed and that’s not what’s happening here. That approach would work, but to avoid possible confusion I used another method.

Instead of using IDisposable, I gave the class the following Enabled property.

// Enable or disable the selector.
private bool IsEnabled = true;
public bool Enabled
{
    get
    {
        return IsEnabled;
    }
    set
    {
        if (value == IsEnabled) return;

        IsEnabled = value;
        if (IsEnabled)
        {
            Host.MouseDown += Host_MouseDown;
        }
        else
        {
            Host.MouseDown -= Host_MouseDown;
            Host.MouseMove -= Host_MouseMove;
            Host.MouseClick -= Host_MouseClick;
            Host.Paint -= Host_Paint;
        }
    }
}

The IsEnabled field keeps track of whether the polygon selector is enabled. The Enabled property gets and sets that value. The get accessor simply returns the value of IsEnabled.

The set accessor first compares IsEnabled to the property’s new value and returns without doing anything if the values are the same.

If the value is changing, the code saves the new value in the IsEnabled field. If the polygon selector should be enabled, the code then installs the Host_MouseDown event handler so the selector is ready to select a polygon.

If the polygon selector should be disabled, the code uninstalls all of the event handlers that the selector might have installed. One nice thing about C#’s event handlers is that you can uninstall an event handler even if it is not installed without anything bad happening. For example, if the Host_Paint event handler is not installed, then uninstalling it won’t hurt the program. (This is good because C# does not provide a way to determine whether an event handler is installed. If this safety feature were not true, then we would need to write code to keep track of which event handlers were installed.)

The Main Program

Building the polygon selector takes a little work, but all of that work makes using the selector relatively simple. The following code shows how the example program creates its selector.

// The polygons.
private List<Point[]> Polygons = new List<Point[]>();

// The polygon selector.
private PolygonSelector Selector;

// Prepare the selector.
private void Form1_Load(object sender, EventArgs e)
{
    Selector = new PolygonSelector(picCanvas, new Pen(Color.Red, 3));
    Selector.PolygonSelected += Selector_PolygonSelected;
}

The Polygons field holds a list of arrays of points. Each of the arrays contains the points that define a polygon.

The Selector field holds the polygon selector.

The form’s Load event handler creates the selector object. Its host is the picCanvas PictureBox control. The code also registers the following event handler to catch the selector’s PolygonSelected event.

// The user has selected a polygon. Save it.
void Selector_PolygonSelected(object sender, PolygonEventArgs args)
{
    // Save the new polygon.
    Polygons.Add(args.Points.ToArray());

    // Redraw.
    picCanvas.Refresh();
}

This code converts the new polygon’s point list into an array and adds it to the Polygons list. It then refreshes the picCanvas control to draw the new polygon.

The following code shows the picCanvas control’s Paint event handler.

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

    using (Pen pen = new Pen(Color.Green, 3))
    {
        foreach (Point[] polygon in Polygons)
        {
            e.Graphics.DrawPolygon(pen, polygon);
        }
    }
}

This code clears the drawing surface and prepares to draw smooth lines. It then loops through the polygons in the Polygons list and draws them.

When the polygon selector refreshes its host control, this event handler executes before the one defined by the polygon selector. That means any partially selected polygon is drawn in top of the polygons drawn by the preceding code. This also means the partially selected polygon is drawn with smooth lines because this code has already set the Graphics object’s SmoothingMode property.

The last piece of the main program is the following code, which executes when the user checks or unchecks the Draw Polygons checkbox.

private void chkDrawPolygons_CheckedChanged(object sender, EventArgs e)
{
    Selector.Enabled = chkDrawPolygons.Checked;
}

This event handler simply updates the polygon selector’s Enabled property appropriately.

Conclusion

Defining the polygon selector symbiont takes a bit of work, but using it is relatively easy. This class allows you to add polygon selection to a control without rewriting the same event handlers over and over. It also lets you avoid cluttering up your form code with those event handlers.

Better still, you can write other selector classes to let the user do other things such as drawing lines, rectangles, ellipses, or performing just about any other sequence of mouse events. Each of the symbionts encapsulates its event handlers to keep your form clean and simple. The most complicated thing you need to do is to ensure that only one symbiont is enabled at a given time.

Download the example to see additional details, to try building new drawing symbionts, or to use this symbiont in your programs. In my next post, I’ll use the PolygonSelector to show how to clip an image to fit within a polygon.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Arrange images on the corners of a polygon in C#


[arrange images]

This post shows a way to arrange images on top of a background image as shown in the image above.

How to Use the Program

Enter the names of the background and foreground image files, or use the File menu’s Foreground Image and Background Image commands to select the files. Next, enter the final size that you want the composite image to have. The program will use an area with this size from the upper left corner of the background image to produce the final result, so make sure this image is at least as large as the values you enter here. If the background image is too small, parts of the result image will be blank. (You could modify the program to scale the background image if necessary.)

Now enter the width that you want the foreground image to have. The program scales that image uniformly so it calculates an appropriate image height from the width you enter.

Next enter a radius to determine the distance between the center of the result image and the centers of the foreground images. Finally enter the number of copies of the foreground image that you want to display. For example, if you enter 6, then the images are placed on the corners of a hexagon.

How the Program Works

The program is relatively straightforward. The basic idea is to make an angle theta loop around a circle, incrementing by the number of radians between the vertices of the polygon. For example, if we’re building a hexagon, then the values of theta are 2π/6 radians apart. For each value of theta, the program finds a point in the direction of theta from the origin at the distance given by the radius value. It then adds that point’s coordinates to the center of the image to center the result. (This is a common approach for doing something to points on a circle. For example, you could connect the points to draw the polygon.)

As it generates its points, the program draws copies of the foreground image at them.

To simplify the discussion somewhat, I’ll describe the program’s code in two pieces. The following code shows how the first piece, which prepares the foreground image.

private void btnGo_Click(object sender, EventArgs e)
{
    try
    {
        Bitmap fg_image = new Bitmap(txtFgImage.Text);
        Bitmap bg_image = new Bitmap(txtBgImage.Text);
        int width = int.Parse(txtWidth.Text);
        int height = int.Parse(txtHeight.Text);
        int fg_width = int.Parse(txtFgWidth.Text);
        int radius = int.Parse(txtRadius.Text);
        int num_images = int.Parse(txtNumImages.Text);

        // Scale the foreground image.
        float scale = fg_width / (float)fg_image.Width;
        int fg_height = (int)(fg_image.Height * scale);
        fg_image = new Bitmap(fg_image, new Size(fg_width, fg_height));

This code gets the images and the parameters that you entered. It then scales the foreground image. To do that, it sets variable scale equal to the foreground image width that you entered divided by the foreground image’s actual width. It then uses that scale factor to calculate the appropriate foreground image height.

The program then sets variable fg_image to a new Bitmap. It passes the constructor the original foreground image and the new size that it should have. This is perhaps the easiest way to resize an image.

The following shows the rest of the code that produces the result image. This piece draws the background image and then draws the foreground image on top of it.

        // Draw the final result.
        Bitmap bm = new Bitmap(width, height);
        using (Graphics gr = Graphics.FromImage(bm))
        {
            gr.InterpolationMode = InterpolationMode.High;
            gr.DrawImage(bg_image, 0, 0);
            int cx = width / 2;
            int cy = height / 2;
            int rx = fg_width / 2;
            int ry = fg_height / 2;
            RectangleF src_rect =
                new RectangleF(0, 0, fg_width, fg_height);

            double theta = -Math.PI / 2;
            double dtheta = 2 * Math.PI / num_images;
            for (int i = 0; i < num_images; i++)
            {
                float x = (float)(radius * Math.Cos(theta));
                float y = (float)(radius * Math.Sin(theta));
                PointF[] dest_points =
                {
                    new PointF(cx + x - rx, cy + y - ry),
                    new PointF(cx + x + rx, cy + y - ry),
                    new PointF(cx + x - rx, cy + y + ry),
                };
                gr.DrawImage(fg_image, dest_points, src_rect, GraphicsUnit.Pixel);
                theta += dtheta;
            }

            // Redraw the left side of the first image.
            src_rect =
                new RectangleF(0, 0, fg_width / 2f, fg_height);

            theta = -Math.PI / 2;
            {
                float x = (float)(radius * Math.Cos(theta));
                float y = (float)(radius * Math.Sin(theta));
                PointF[] dest_points =
                {
                    new PointF(cx + x - rx, cy + y - ry),
                    new PointF(cx + x, cy + y - ry),
                    new PointF(cx + x - rx, cy + y + ry),
                };
                gr.DrawImage(fg_image, dest_points, src_rect, GraphicsUnit.Pixel);
            }
        }
        picResult.Image = bm;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

This code creates a bitmap with the final size that you entered. It makes an associated Graphics object and draws the background image onto it. The only parameters that the code passes into the DrawImage method are the image and the coordinates (0, 0) were the image’s upper left corner should be drawn. The image is drawn at full scale and truncated if it cannot fit on the bitmap.

Next the code calculates the coordinates of the center of the bitmap. It also makes a RectangleF sized to fit the foreground image.

The code then makes variable theta loop through angles around a circle. It starts at angle π/2 so the first image is directly above the center of the bitmap.

For each angle, the code calculates the point (x, y) distance radius from the origin. It adds those values to the coordinates at the center of the result image. It also adds +/-rx and +/-ry to find the corners where the new copy of the foreground image should be placed. Finally the code calls the Graphics object’s DrawImage method to draw a copy of the foreground image in that position.

If the program left it at that, the first image would be below the last one as shown in the following picture.


[arrange images]

To make the final image lies partly below the first one, the code redraws the left side of the first image again. Note that this doesn’t work if the images are so close together that images before the last one also overlap with the first image.

Conclusion

This example lets you arrange images at the corners of a polygon. You may not need this in a line-of-business application, but it produces an interesting effect that you may find useful. It’s also an worthwhile exercise in image processing.

Note that this program does not rotate the copies of the foreground image. You can see this if you look closely at the images above. It’s easier to see in the following image.


[arrange images]

If you want the images to rotate so they all have their bottoms closer to the center of the result, you can use techniques described in my earlier post Draw an image spiral in C#.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Make an improved diagonal picture montage in C#


[picture montage]

My earlier example Make a Pinterest-style diagonal picture montage in C# showed how you could make a picture montage showing parts of images rotated by a desired angle. While using that program today I found a small bug. The areas between the images are not always completely filled with the divider color.

The following picture shows the program displaying a picture montage that it created. The red circles show places where pieces of images show between the cells’ borders.


[picture montage]

[picture montage]

The picture on the right shows aa closeup of one of the problem areas.

Fortunately the solution to this problem is simple. The Cell class’s Draw method draws a cell’s picture and border. The following code shows the part of that method that draws the border. The new statement highlighted in blue fixes the problem.


// Draw the cell.
public void Draw(Graphics gr, Pen pen, float cell_width, float cell_height)
{
    // Draw the cell's picture.
    ...

    // Outline the cell.
    gr.DrawRectangle(pen, Bounds.X, Bounds.Y, Bounds.Width, Bounds.Height);
    GraphicsPath path = MakeRoundedRect(Bounds,
        2 * pen.Width, 2 * pen.Width, true, true, true, true);
    gr.DrawPath(pen, path);
}

The new statement draws a rectangle around the image before the code draws the image’s rounded rectangle. The new rectangle fills in the problem areas where the rounded rectangles’ corners don’t quite meet the adjacent rectangles.

See the Make a Pinterest-style diagonal picture montage in C#previous post for more details about how the program works. Download this example to experiment with it.


Download Example   Follow me on Twitter   RSS feed   Donate




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