Make a blended composite image in C#

[composite image]

The other day I wanted to do something that all of us do from time to time: make a composite image with the Eye of Sauron on top of another picture. This turned out to be a bit more complicated than I had hoped it would be.


Desired Results

Just making a composite image isn’t too hard. For example, you can use the Graphics class’s DrawImage method to draw one image on top of another. The result is a rectangular image dropped on top of the background image as shown in the picture below.


[composite image]

This probably isn’t quite what you had in mind.

You can improve the result by using the foreground image to make a TextureBrush and then filling an ellipse with that brush. The result is an ellipse filled with part of the foreground image, as shown in the following picture.


[composite image]

This is much better, but it’s still not perfect because the top image still has a sharp edge, even if it is now elliptical.

What I really want is the top image to fade away as it approaches its edges. The picture on the left below shows the Eye of Sauron fading away near its edges. The picture on the right shows the result when you place this image on top of the background image.


[composite image] [composite image]

What we really need is a brush that fades away at its edges.

Alternatively, it would be nice to have a brush that only applies to an image’s alpha (transparency) channel. We could use that brush to make the alpha values of the foreground image fade away as shown in the picture on the left above. Then we could draw the result on top of the background image.

Unfortunately .NET doesn’t give us that kind of brush. However, we can create a brush that makes a solid color fade away at the edges of an ellipse. Then we can write a method that copies only the alpha values of the resulting image’s pixels onto the foreground image. The result is the foreground image fading toward the edges of the ellipse.

Fading To The Edges

When you select the File menu’s Foreground Image command, the following code lets you select a new foreground image.

private Bitmap BgImage = null;
private Bitmap FgImage = null;
private RectangleF FgRect = new RectangleF(-10, -10, 1, 1);

private void mnuFileForegroundImage_Click(object sender, EventArgs e)
{
    if (ofdImage.ShowDialog() == DialogResult.OK)
    {
        FgImage = LoadBitmapUnlocked(ofdImage.FileName);
        FgRect = new RectangleF(0, 0,
            FgImage.Width, FgImage.Height);
        AspectRatio = (float)FgImage.Width / (float)FgImage.Height;

        // Make a bitmap that fades from alpha = 255
        // at the center to alpha = 0 at the egdes.
        Bitmap ellipse_bm =
            new Bitmap(FgImage.Width, FgImage.Height);
        using (Graphics gr = Graphics.FromImage(ellipse_bm))
        {
            gr.Clear(Color.Transparent);
            using (GraphicsPath path = new GraphicsPath())
            {
                path.AddEllipse(FgRect);
                using (PathGradientBrush brush =
                    new PathGradientBrush(path))
                {
                    brush.CenterPoint = new PointF(
                        FgImage.Width / 2f,
                        FgImage.Height / 2f);
                    brush.CenterColor = Color.White;
                    brush.SurroundColors = new Color[] { Color.FromArgb(0, 0, 0, 0) };

                    Blend blend = new Blend();
                    blend.Positions = new float[] { 0.0f, 0.5f, 1.0f };
                    blend.Factors = new float[] { 0.0f, 1.0f, 1.0f };
                    brush.Blend = blend;

                    gr.FillPath(brush, path);
                }
            }
        }

        // Copy the alpha values from ellipse_bm to FgRect.
        CopyAlpha(FgImage, ellipse_bm);

        // Show the result.
        picImage.Refresh();
    }
}

Variables BgImage and FgImage hold the foreground and background images. The value FgRect holds the foreground image’s current location and size. When you resize the selection area, the program updates this value to hold the new selection.

The menu item’s Click event handler displays an OpenFileDialog to let you select the foreground image file and loads it into the FgImage variable. It then sets the FgRect value to place the image at its full size and at position (0, 0). The code also calculates the image’s aspect ratio.

Next, the code creates a new bitmap that has the same size as the foreground image to hold an ellipse that fades toward its edges. The code makes an associated Graphics object and clears it with the Transparent color.

The code then makes a GraphicsPath and adds to it an ellipse that fills the bitmap’s area.

It then creates a PathGradientBrush defined by the path. This creates a brush that makes colors fade from a defined center point (which need not actually be in the center of the path) to the points on the path’s edges.

The code sets the brush’s center point to be at the ellipse’s center and sets CenterColor to white so the brush starts with white at its center.

The program then sets the brush’s SurroundColors value to an array holding the colors that the brush should use for the points along the path. This example uses a single color, so the brush repeats that color as needed for the path’s points. This example uses the color black, but the only important thing here is that the surround color’s alpha component is 0 so the color is transparent.

After some experimentation, I decided that the image didn’t look its best if the colors blended linearly from white at the center to transparent at the ellipse’s edges. In that case the background image showed through starting at the image’s center.

Instead I wanted the color to remain solid until partway to the edge and then start to fall off into transparency. To do that, the code creates a Blend object.

The Positions array indicates the fraction of the distance from the path’s edge points to the center point where we want to define the colors. The Factors array indicates the fraction of the center and edge colors that should be used at the corresponding position.

For example, the position 0.0 means a point at the path’s edge. The factor 0.0 means those points should be 0.0 times the center color and 1.0 – 0.0 = 1.0 times the edge color.

The position 0.5 and factor 1.0 means that points halfway between the edge and center should be 1.0 times the center color and 1.0 – 1.0 = 0.0 times the edge color.

Finally the position 1.0 and factor 1.0 means that points at the center should be 1.0 times the center color and 0.0 times the edge color.

In this example, that means the brush is solid white from the center to halfway to the edge and then fades off to transparent at the edges of the path.

Having created the brush, the program simply fills the new image with the brush.

At this point we have an image that is the same size as the foreground image and that fades from white to transparent. Now the mnuFileForegroundImage_Click event handler calls the CopyAlpha method described next to copy the alpha values from this image onto the foreground image.

The event handler finishes by refreshing the picImage PictureBox control so its Paint event handler can draw the background image with the foreground image on top of it.

Combining Alpha Values

The CopyAlpha method shown below copies the alpha components of the pixels in one image to the pixels of another image.

// Copy the alpha values from mask to bm.
private void CopyAlpha(Bitmap bm, Bitmap mask)
{
    Bitmap32 bm32 = new Bitmap32(bm);
    Bitmap32 mask32 = new Bitmap32(mask);
    bm32.LockBitmap();
    mask32.LockBitmap();

    for (int x = 0; x < bm.Width; x++)
    {
        for (int y = 0; y < bm.Height; y++)
        {
            bm32.SetAlpha(x, y, mask32.GetAlpha(x, y));
        }
    }
    mask32.UnlockBitmap();
    bm32.UnlockBitmap();
}

The Bitmap class has GetPixel and SetPixel methods that you can use to do this. Unfortunately those methods are pretty slow. The CopyAlpha method uses the Bitmap32 class described in Use the Bitmap32 class to manipulate image pixels very quickly in C# to do the same thing much more quickly.

The method creates Bitmap32 objects representing the main image and the mask image that defines the alpha values. It then loops through the images’ pixels.

For each pixel, the method uses GetAlpha to get the mask pixel’s Alpha value. It then uses SetAlpha to set the main pixel’s alpha value.

Paint

The following code shows how the program’s Paint event handler draws the background image with the foreground image on top.

// Draw the selection rectangle.
private const int HandleRadius = 4;
private void picImage_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    e.Graphics.InterpolationMode = InterpolationMode.High;

    try
    {
        // Get the scaled selection rectangle.
        RectangleF scaled_rect = ScaledSelectionRectangle();

        // Draw the foreground image.
        if (FgImage != null)
        {
            e.Graphics.DrawImage(FgImage, scaled_rect);
        }

        // Draw the selection rectangle.
        using (Pen pen = new Pen(Color.Red, 2))
        {
            e.Graphics.DrawRectangle(pen, scaled_rect);

            pen.Color = Color.Yellow;
            pen.DashPattern = new float[] { 5, 5 };
            e.Graphics.DrawRectangle(pen, scaled_rect);
        }

        PointF[] corners =
        {
            new PointF(scaled_rect.Left, scaled_rect.Top),
            new PointF(scaled_rect.Right, scaled_rect.Top),
            new PointF(scaled_rect.Left, scaled_rect.Bottom),
            new PointF(scaled_rect.Right, scaled_rect.Bottom),
        };
        foreach (PointF point in corners)
        {
            e.Graphics.DrawBox(Brushes.White, Pens.Black, point, HandleRadius);
        }
    }
    catch
    {
    }
}

After setting the Graphics object’s SmoothingMode and InterpolationMode properties, the code calls the ScaledSelectionRectangle method to get the currently selected area.

This example uses techniques described in the post Crop scaled images to a desired aspect ratio in C# to let the user select areas on the image even if the image is scaled. It’s a fairly involved technique so I don’t want to describe it again here. Download that example to see how it works.

After it has the scaled selection rectangle, the Paint event handler draws the draws the foreground image in that rectangle. There are a couple of things to note here. First, the image stored in FgImage has been modified by the CopyAlpha method so it fades from its center toward its edges. The program makes the image image fade when it loads the image, so it doesn’t need to worry about that again here.

Second, notice that the Paint event handler does not draw the background image. When you load the background image or change the scale, the program sets the picImage control’s Image property to the scaled background image. That means when the Paint event handler executes, the background image is already automatically drawn. The Paint event handler just needs to draw anything that should be on top of the background image.

After it draws the foreground image in the current selection rectangle, the code draws the rectangle. It makes a red pen and draws the rectangle. It then changes the pen to yellow, gives it a dash pattern, and redraws the rectangle. The result is a red and yellow dashed rectangle.

The code then creates an array holding the selection rectangle’s corners. It loops through those corners and uses the DrawBox extension method to draw white rectangles at the corners. The DrawBox extension method is relatively simple so I won’t show it here. Download the example to see how it works.

Conclusion

The most important technique used by this example is the way it prepares the foreground image when it is loaded. It creates an ellipse that shades from solid white to transparent at its edges. It then uses the CopyAlpha method to copy the alpha components from the ellipse to the foreground image. After that, using the foreground image is relatively simple.

I know that this example includes a ton of other details that I don’t cover here. In particular it uses techniques described in the post Crop scaled images to a desired aspect ratio in C# to let you select an area in a scaled image while preserving the foreground image’s aspect ratio. I’m sorry that I’ve left so much out of this post (and even the previous one), but this would be a very long post if I included every details.

Download the example program and look at the earlier example to see additional details. Overall I think you’ll find that this program works pretty well and lets you make some interesting images.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Crop scaled images to a desired aspect ratio in C#

[aspect ratio]

I often need to crop images and sometimes I want a specific aspect ratio. (In case you haven’t heard this term before, the aspect ratio is the ratio of the image’s width to height. As an equation, it’s width / height.) For example, depending on the project, I might want a square 1:1 aspect ratio, the 4:3 aspect ratio that phones produce, or the 3:4 aspect ratio for a vertically oriented image. (Most of these pictures are for the web site and Facebook feed for my bakery The Enchanted Oven, although I have not cropped all of those pictures to specific aspect ratios.)

I’ve written several cropping examples in the past and you can use them to do this, but they’re cumbersome. For example, you can figure out how tall the cropped area should be and then calculate how wide it must be to give the desired aspect ratio.

The example in this post is the one I really wanted all along, but I knew it was going to be hard so I kept putting it off. As I expected it would be, it is fairly complicated so it requires a fairly long explanation.

This example is too long for me to post all of its code so I’m just going to cover some of its most interesting parts. Download the example to experiment with it and to see additional details. And there are a lot of additional details!

Features

The example is fairly easy to use, but it provides a lot of features so it takes a while to describe.

To use the program, open the File menu and select Open to pick an image file. Use the Scale menu to scale the image.

This example displays three text boxes where you can adjust the selection area. Enter the aspect ratio that you want in the first text box. If you change the aspect ratio, the program must update the rectangle’s width or height so it matches the new ratio. To do this, it enlarges one of those values, keeping the rectangle centered over its original position.

If you enter a new width in the second text box, the program updates the height in the third text box to keep the same aspect ratio. Conversely if you enter a new height in the third text box, the program updates the width in the second text box to maintain the aspect ratio. When you use the width and height text boxes to change the rectangle’s dimensions, the program centers the rectangle over its previous center.

In addition to using the width and height text boxes, you can modify the selection area by clicking and dragging.

If you click the body of the selection area, you can move the selection rectangle to a new location.

If you click one of the selection area’s corners, you can move that corner while leaving the opposite corner unmoved. The program adjusts the width and height to maintain the desired aspect ratio.

If you click and drag one of the selection area’s sides, you can change the area’s width or height, again maintaining the desired aspect ratio. The program leaves the opposite side unmoved. It centers the other dimension over the selection area’s current location.

For example, suppose you drag the selection area’s bottom edge downward. That increases the area’s height, so the program must also increase its width to keep the aspect ratio unchanged. The program adjusts the enlarged selection area so it has the same top edge and its left and right edges are centered over its original horizontal center.

After you have selected the area that you want, use the File menu’s Save As command to save the selected area into a new file.

The example’s final feature is the Rectangle menu’s Reset command. Sometimes you may accidentally drag the selection rectangle completely off of the form. For example, suppose you move the rectangle so it hangs off of the form’s left edge. If you then drag the area’s right side to the left so it is also off of the form, you won’t be able to get the rectangle back. You can probably recover the rectangle by using the width and height text boxes to make it big enough to overlap the image and then drag it into a better position, but that could be a lot of work. If you select the Rectangle menu’s Reset command, the program moves the rectangle so it’s upper left corner is at position (10, 10). You can then drag it to a better position or resize it as needed.

Managing Text Boxes

The program’s three text boxes provide an interesting problem. If you change the value in one of them, the others may need to update to show new values.

One way you could handle this is to use TextChanged event handlers to look for changes. Unfortunately when you update one of the other text boxes, it will fire its TextChanged event. That may make the program update other text boxes and that causes yet another TextChanged event. In the worst case, you could enter an infinite series of events.

Most controls are smart enough to not fire their changed events if the value is changing from a value to that same value, so eventually the event chain should stop, possibly after a round of unnecessary changes. In general, however, this kind of sequence may be risky if you are using other controls that always fire their changed events or if rounding errors make the values change every time. In any case, it’s a waste of time.

To avoid these sorts of changes, the program uses a variable named IgnoreTextChanged. Before it updates any of the text boxes, the program sets this value to true. The TextChanged event handlers then check this value and, if it is true, they return without doing anything.

For example the following code shows the program’s SetWidth method, which, as you can probably guess, sets the selection area’s width.

private bool IgnoreTextChanged = false;
private void SetWidth(float width)
{
    RectHeight = (float)(width / AspectRatio);
    IgnoreTextChanged = true;
    txtHeight.Text = RectHeight.ToString();
    IgnoreTextChanged = false;
    SetSelectionRectangle();
}

This method calculates the height that the selection area must have to preserve the current aspect ratio and then sets IgnoreTextChanged to true. Next the code displays the new height in the txtHeight text box. It resets IgnoreTextChanged to false and calls the SetSelectionRectangle method to position the resized selection rectangle so it is centered over its previous position.

The following code shows the txtHeight control’s TextChanged event handler.

private void txtHeight_TextChanged(object sender, EventArgs e)
{
    if (IgnoreTextChanged) return;
    if (!float.TryParse(txtHeight.Text, out RectHeight)) return;
    SetHeight(RectHeight);
}

This event handler checks the value of IgnoreTextChanged and returns if it is true.

This is a fairly common technique for avoiding update event loops. Use a variable to indicate when the program is updating controls such as text boxes. Those controls’ update event handlers should check this value and do nothing if the value is true.

The Selectiong Rectangle

One complicating factor that runs throughout the example is that this program lets you scale the image while you work on it. That means values such as the selection rectangle must sometimes be scaled along with the image.

The variable SelectionRectangle defined by the following statement keeps track of the current selection rectangle in image coordinates.

private RectangleF SelectionRectangle;

For example, if the selection rectangle is 100 pixels wide, then the area that it represents on the scaled image is 100 pixels wide.

Often the program needs to work with the scaled selection rectangle. For example, if you are working on an image at 50% scale, then the program must draw the selection rectangle at 50% scale. To make that a little easier, the following ScaledSelectionRectangle method returns the rectangle scaled.

// Scale the selection rectangle.
private RectangleF ScaledSelectionRectangle()
{
    float x = ImageScale * SelectionRectangle.X;
    float y = ImageScale * SelectionRectangle.Y;
    float wid = ImageScale * SelectionRectangle.Width;
    float hgt = ImageScale * SelectionRectangle.Height;
    return new RectangleF(x, y, wid, hgt);
}

This method simply scales the rectangle’s left, top, width, and height values. It uses those values to create the scaled rectangle and returns the result. The program can then draw the scaled rectangle, see if the mouse is over the scaled rectangle, or otherwise work with the scaled rectangle.

Mouse Movements

The example tracks mouse movement much as other programs do. When you are not dragging anything, the MouseMove event handler changes the cursor to indicate the part of the selection rectangle below the mouse. If you click over the selection rectangle’s corners or edges, the program lets you resize the rectangle. If you click on the selection rectangle’s body, the program lets you move the rectangle.

The FindHitType helper method returns a value indicating the part of the selection rectangle that is at a specified position. The following code shows the method and the HitTypes enumeration that it returns.

private enum HitTypes
{
    None,
    Body,
    LeftEdge,
    RightEdge,
    TopEdge,
    BottomEdge,
    ULCorner,
    URCorner,
    LLCorner,
    LRCorner,
};

private const int HandleRadius = 4;

private HitTypes FindHitType(Point point)
{
    RectangleF scaled_rect = ScaledSelectionRectangle();
    bool hit_left, hit_right, hit_top, hit_bottom;
    hit_left =
        ((point.X >= scaled_rect.Left - HandleRadius) &&
         (point.X <= scaled_rect.Left + HandleRadius));
    hit_right =
        ((point.X >= scaled_rect.Right - HandleRadius) &&
         (point.X <= scaled_rect.Right + HandleRadius));
    hit_top =
        ((point.Y >= scaled_rect.Top - HandleRadius) &&
         (point.Y <= scaled_rect.Top + HandleRadius));
    hit_bottom =
        ((point.Y >= scaled_rect.Bottom - HandleRadius) &&
         (point.Y <= scaled_rect.Bottom + HandleRadius));

    if (hit_left && hit_top) return HitTypes.ULCorner;
    if (hit_right && hit_top) return HitTypes.URCorner;
    if (hit_left && hit_bottom) return HitTypes.LLCorner;
    if (hit_right && hit_bottom) return HitTypes.LRCorner;
    if (hit_left) return HitTypes.LeftEdge;
    if (hit_right) return HitTypes.RightEdge;
    if (hit_top) return HitTypes.TopEdge;
    if (hit_bottom) return HitTypes.BottomEdge;
    if ((point.X >= scaled_rect.Left) && (point.X <= scaled_rect.Right) &&
        (point.Y >= scaled_rect.Top) && (point.Y <= scaled_rect.Bottom))
            return HitTypes.Body;
    return HitTypes.None;
}

The method first gets the scaled selection rectangle so it has the rectangle defined in pixels. The point passed into the method is the mouse position, which is in pixels seen on the screen, so the method must compare that position to the rectangle when it is scaled.

The method then simply compares the specified point’s coordinates to the rectangle’s coordinates to see whether the point is over the rectangle’s corners, edges, or body and returns the appropriate result.

The program uses the hit type in several ways. The most complicated of those ways occurs when you click and drag the mouse over the selection rectangle’s parts.

The CurrentHitType and Dragging variables are set in the MouseDown event handler. That method is relatively straightforward so I won’t show it here. It simply calls FindHitType to get the hit type appropriate for the mouse’s position and then sets Dragging to true.

The following code executes when you move the mouse.

private void picImage_MouseMove(object sender, MouseEventArgs e)
{
    if (!Dragging) MouseMoveNotDragging(e.Location);
    else MouseMoveDragging(e.Location);
}

If the Dragging variable is false, the code calls the MouseMoveNotDragging to display an appropriate cursor for the current mouse position. That method is also relatively straightforward so I won’t show it here. The only non-obvious part to that method that I want to describe is the OppositeCorner variable. The method sets that variable equal to the selection rectangle’s corner that is opposite to the point under the mouse. This doesn’t really matter if the mouse is not over a selection rectangle corner.

If the Dragging variable is true, then the mouse is down and you are dragging. In that case the code calls the MouseMoveDragging method. That method is fairly long, so I’m going to describe it in pieces. Here’s the first piece.

private void MouseMoveDragging(Point point)
{
    // Find the new size for corner drags.
    float corner_wid = Math.Abs(OppositeCorner.X - point.X / ImageScale);
    float corner_hgt = Math.Abs(OppositeCorner.Y - point.Y / ImageScale);
    SizeF corner_size = GetReducedSize(corner_wid, corner_hgt);
    ...

This code subtracts the mouse’s coordinates from the OppositeCorner to see how wide and tall the selection rectangle should be. Notice that the code scales the mouse’s coordinates by dividing them by the current image scale to convert from screen coordinates to the image’s scaled coordinates. That puts all of the coordinates in the selection rectangle’s coordinate system.

The code then calls the GetReducedSize method to adjust the width and height so they satisfy the desired aspect ratio. The following code shows the GetReducedSize method.

private SizeF GetReducedSize(float new_width, float new_height)
{
    if (new_width < 10) new_width = 10;
    if (new_height < 10) new_height = 10;

    if (new_width / new_height > AspectRatio)
    {
        // Too short and wide. Decrease the width.
        new_width = new_height * AspectRatio;
    }
    else
    {
        // Too tall and thin. Decrease the height.
        new_height = new_width / AspectRatio;
    }
    return new SizeF(new_width, new_height);
}

This method first ensures that the new width and height are at least 10. It then compares the desired width / height ratio to the current aspect ratio. If the new ratio is too short and wide, then the code decreases the width to match the desired aspect ratio. Conversely if the new ratio is too tall and thin, then the code decreases the height to match the desired aspect ratio.

Notice that in either case, the program reduces the width or height to match the desired aspect ratio. If you run the program and drag one of the selection rectangle’s corners, you’ll see how the rectangle shrinks if necessary. That seems like the natural action.

Here’s the next piece of the MouseMoveDragging method.

    ...
    // Find the new size for edge drags.
    SizeF edge_size = new SizeF();
    if ((CurrentHitType == HitTypes.TopEdge) ||
        (CurrentHitType == HitTypes.BottomEdge))
            edge_size = GetEnlargedSize(0, corner_hgt);
    else if ((CurrentHitType == HitTypes.LeftEdge) ||
        (CurrentHitType == HitTypes.RightEdge))
            edge_size = GetEnlargedSize(corner_wid, 0);
    ...

This code is similar to the earlier code except it works for edge drags instead of corner drags. If this is an edge drag, the code calls the GetEnlargedSize method, passing it the appropriate width or height for the new rectangle. (Depending on the edge that you are dragging.)

The following code shows the GetEnlargedSize method.

pivate SizeF GetEnlargedSize(float new_width, float new_height)
{
    if (new_width < 10) new_width = 10;
    if (new_height < 10) new_height = 10;

    if (new_width / new_height > AspectRatio)
    {
        // Too short and wide. Increase the height.
        new_height = new_width / AspectRatio;
    }
    else
    {
        // Too tall and thin. Increase the width.
        new_width = new_height * AspectRatio;
    }
    return new SizeF(new_width, new_height);
}

This method is similar to the GetReducedSize method except it enlarges the rectangle if necessary instead of shrinking it. If you run the program and drag one of the selection rectangle’s edges, you’ll see how the rectangle enlarges if necessary. That seems like the natural action.

Now back to the MouseMoveDragging method. Here’s the next piece.

    ...
    // Find the center of the selection rectangle for edge drags.
    float cx = SelectionRectangle.X + SelectionRectangle.Width / 2f;
    float cy = SelectionRectangle.Y + SelectionRectangle.Height / 2f;
     ...

This code simply finds the center of the current selection rectangle.

The following code shows most of the rest of the MouseMoveDragging method.

    ...
    switch (CurrentHitType)
    {
        // Corners.
        case HitTypes.ULCorner:
            SelectionRectangle = new RectangleF(
                SelectionRectangle.Right - corner_size.Width,
                SelectionRectangle.Bottom - corner_size.Height,
                corner_size.Width, corner_size.Height);
            break;
        case HitTypes.URCorner:
            SelectionRectangle = new RectangleF(
                SelectionRectangle.Left,
                SelectionRectangle.Bottom - corner_size.Height,
                corner_size.Width, corner_size.Height);
            break;
        case HitTypes.LRCorner:
            SelectionRectangle = new RectangleF(
                SelectionRectangle.X,
                SelectionRectangle.Y,
                corner_size.Width, corner_size.Height);
            break;
        case HitTypes.LLCorner:
            SelectionRectangle = new RectangleF(
                SelectionRectangle.Right - corner_size.Width,
                SelectionRectangle.Top,
                corner_size.Width, corner_size.Height);
            break;

        // Edges.
        case HitTypes.TopEdge:
            SelectionRectangle = new RectangleF(
                cx - edge_size.Width / 2f,
                SelectionRectangle.Bottom - edge_size.Height,
                edge_size.Width, edge_size.Height);
            break;
        case HitTypes.RightEdge:
            SelectionRectangle = new RectangleF(
                SelectionRectangle.Left,
                cy - edge_size.Height / 2f,
                edge_size.Width, edge_size.Height);
            break;
        case HitTypes.BottomEdge:
            SelectionRectangle = new RectangleF(
                cx - edge_size.Width / 2f,
                SelectionRectangle.Top,
                edge_size.Width, edge_size.Height);
            break;
        case HitTypes.LeftEdge:
            SelectionRectangle = new RectangleF(
                SelectionRectangle.Right - edge_size.Width,
                cy - edge_size.Height / 2f,
                edge_size.Width, edge_size.Height);
            break;

        // Body.
        case HitTypes.Body:
            int dx = (int)((point.X - LastPoint.X) / ImageScale);
            int dy = (int)((point.Y - LastPoint.Y) / ImageScale);
            SelectionRectangle.X += dx;
            SelectionRectangle.Y += dy;
            break;
    }
    ...

This code creates a new selection rectangle that is appropriate to the kind of drag you are performing. For example, if you are dragging the rectangle’s left edge (the second-to-last case), the code leaves the rectangle’s right edge unchanged and positions the left edge the correct distances away. It sets the rectangle’s top coordinate so the rectangle is centered vertically at Y position cy.

If you look through each of the cases, you can see that the code adjusts the rectangle to leave the corner or edge opposite the mouse’s position unchanged. You may want to run the program and experiment to see how each of the cases works. As you do, also notice how natural it is that the rectangle shrinks

The following code shows the last part of the MouseMoveDragging method.

    ...
    LastPoint = point;
    picImage.Refresh();
    ShowWidthAndHeight();
}

This code saves the mouse’s position in the variable LastPoint so it can see how far the mouse has moved if you are dragging the selection rectangle’s body. (See the last case block in the switch statement shown earlier.) It refreshes the displayed image and calls ShowWidthAndHeight.

The ShowWidthAndHeight method displays the selection rectangle’s dimensions in the text boxes. That method is fairly straightforward (given the earlier discussion of the IgnoreTextChanged variable described earlier), so I won’t show it here. Download the example to see how it works.

Selecting a Scale

Compared to the MouseMoveDragging method, this is simple. When you select one of the Scale menu’s commands, the following code executes.

private void mnuScale_Click(object sender, EventArgs e)
{
    // Get the scale factor.
    ToolStripMenuItem menu_item =
        sender as ToolStripMenuItem;
    string scale_text = menu_item.Text.Replace("&", "").Replace("%", "");
    ImageScale = float.Parse(scale_text) / 100f;
    ShowScaledImage();

    // Display the new scale.
    mnuScale.Text = "Scale (" + menu_item.Text.Replace("&", "") + ")";

    // Check the selected menu item.
    foreach (ToolStripMenuItem item in mnuScale.DropDownItems)
    {
        item.Checked = (item == menu_item);
    }
}

The code converts the event’s sender into the menu item that raised the event. It uses the menu item’s caption to get the desired scale.

The code then sets the ImageScale value and calls the ShowScaledImage method described shortly to display the scaled image. It then updates the caption of the top-level menu so it displays text such as “Scale (75%)” and finishes by unchecking the other scale menu items.

The following code shows the ShowScaledImage method.

private void ShowScaledImage()
{
    if (OriginalImage == null) return;
    int scaled_width = (int)(OriginalImage.Width * ImageScale);
    int scaled_height = (int)(OriginalImage.Height * ImageScale);
    ScaledImage = new Bitmap(scaled_width, scaled_height);
    using (Graphics gr = Graphics.FromImage(ScaledImage))
    {
        Point[] dest_points =
        {
            new Point(0, 0),
            new Point(scaled_width - 1, 0),
            new Point(0, scaled_height - 1),
        };
        Rectangle src_rect = new Rectangle(
            0, 0,
            OriginalImage.Width - 1,
            OriginalImage.Height - 1);
        gr.DrawImage(OriginalImage, dest_points,
            src_rect, GraphicsUnit.Pixel);
    }
    picImage.Image = ScaledImage;
    picImage.Visible = true;
    picImage.Refresh();
}

This method calculates the scaled image width and height, and uses those to create a Bitmap with the scaled size. It creates an associated Graphics object and uses it to draw the original image onto the scaled Bitmap. The method finishes by displaying the scaled image in the picImage control and making that control visible.

Drawing the Selection Rectangle

The following code shows how the program draws the selection rectangle.

// Draw the selection rectangle.
private const int HandleRadius = 4;
private void picImage_Paint(object sender, PaintEventArgs e)
{
    try
    {
        // Draw the selection rectangle.
        RectangleF scaled_rect = ScaledSelectionRectangle();
        using (Pen pen = new Pen(Color.Red, 2))
        {
            e.Graphics.DrawRectangle(pen, scaled_rect);

            pen.Color = Color.Yellow;
            pen.DashPattern = new float[] { 5, 5 };
            e.Graphics.DrawRectangle(pen, scaled_rect);
        }

        PointF[] corners =
        {
            new PointF(scaled_rect.Left, scaled_rect.Top),
            new PointF(scaled_rect.Right, scaled_rect.Top),
            new PointF(scaled_rect.Left, scaled_rect.Bottom),
            new PointF(scaled_rect.Right, scaled_rect.Bottom),
        };
        foreach (PointF point in corners)
        {
            e.Graphics.DrawBox(Brushes.White, Pens.Black,
                point, HandleRadius);
        }
    }
    catch
    {
    }
}

This code first uses the following ScaledSelectionRectangle method to get a scaled version of the selection rectangle.

// Scale the selection rectangle.
private RectangleF ScaledSelectionRectangle()
{
    float x = ImageScale * SelectionRectangle.X;
    float y = ImageScale * SelectionRectangle.Y;
    float wid = ImageScale * SelectionRectangle.Width;
    float hgt = ImageScale * SelectionRectangle.Height;
    return new RectangleF(x, y, wid, hgt);
}

This helper method simply scales the selection rectangle’s X and Y coordinates, and its width and height. It uses the scaled values to create a scaled rectangle and returns it.

After it has the scaled selection rectangle, the Paint event handler creates a two-pixel-wide red pen and uses it to draw the rectangle. It then changes the pen’s color to yellow, gives it a dash pattern, and draws the rectangle again. The result is a red and yellow dashed rectangle.

Next the code creates an array holding the rectangle’s corners and loops through them calling the following DrawBox extension method to draw them.

public static void DrawBox(this Graphics gr,
    Brush brush, Pen pen, PointF center, float radius)
{
    RectangleF rect = new RectangleF(
        center.X - radius,
        center.Y - radius,
        2 * radius, 2 * radius);
    gr.FillRectangle(brush, rect);
    gr.DrawRectangle(pen, rect);
}

This method creates a Rectangle with the desired width, height, and center. It then fills and outlines the rectangle with the desired brush and pen.

Note that the Paint event handler does not draw the image. The ShowScaledImage method sets the picImage control’s Image property to the scaled image. After that, the control automatically redisplays the image when necessary. All the Paint event handler needs to do is draw the selection rectangle on top of it.

Also note that the Paint event handler does not call e.Graphics.Clear to clear the drawing area. If it did that, it would erase the image.

Saving the Result

When you select the File menu’s Save As command, the following code executes.

// Save the selected area.
private void mnuFileSaveAs_Click(object sender, EventArgs e)
{
    if (sfdImage.ShowDialog() == DialogResult.OK)
    {
        try
        {
            // Copy the selected area into a new Bitmap.
            Bitmap bm = new Bitmap(
                (int)SelectionRectangle.Width,
                (int)SelectionRectangle.Height);
            using (Graphics gr = Graphics.FromImage(bm))
            {
                gr.DrawImage(OriginalImage, 0, 0,
                    SelectionRectangle,
                    GraphicsUnit.Pixel);
            }

            // Save the new Bitmap.
            SaveImage(bm, sfdImage.FileName);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }
}

This code displays the sfdImage SaveFileDialog so you can indicate where you want to save the file. If you pick a file and click Save, then the program creates a Bitmap with the selection rectangle’s dimensions. It copies the piece of the original image that lies below the selection rectangle onto this bitmap. Finally it calls the SaveImage method to save the image. For information on that method, see Save images with an appropriate format depending on the file name’s extension in C#.

Conclusion

I know this is a long post, but it still doesn’t cover a lot of details. Hopefully the pieces that it covers will help you figure out the rest of it. Download the example to see additional details.

You should at least download the program and experiment with it. I’ve found it a remarkably intuitive and useful tool for cropping images.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Colorize images in C#


[colorize]

This example produces some impressive results, but it’s actually quite simple. It uses ImageAttribute techniques demonstrated by several other examples to quickly manipulate an image’s colors.

Adjust Color

The following AdjustColor method starts the process of coloring an image.

// Adjust the image's colors.
private Image AdjustColor(Image image)
{
    // Make the ColorMatrix.
    ColorMatrix cm = GetColorMatrix();
    ImageAttributes attributes = new ImageAttributes();
    attributes.SetColorMatrix(cm);

    // Make the result image.
    return image.CopyImage(attributes);
}

This method calls the GetColorMatrix method described shortly to get a ColorMatrix object holding the values that you entered in the text boxes. It then creates an ImageAttribute object and uses its SetColorMatrix method to store the ColorMatrix in the ImageAttribute object. It finishes by calling the CopyImage extension method (also described shortly) to apply the color matrix to the image and returns the result.

GetColorMatrix

The following code shows the GetColorMatrix method.

// Return the matrix entered by the user.
private ColorMatrix GetColorMatrix()
{
    float[][] values = GetMatrix();
    if (values == null) return null;
    return new ColorMatrix(values);
}

The GetColorMatrix method calls the GetMatrix method to get a float[][] holding the values that you entered into the program’s text boxes.

The following code shows the GetMatrix method.

private float[][] GetMatrix()
{
    float[][] values = new float[][]
    {
        new float[5],
        new float[5],
        new float[5],
        new float[5],
        new float[5],
    };
    for (int i = 0; i < 5; i++)
    {
        for (int j = 0; j < 5; j++)
        {
            float value;
            if (!float.TryParse(TextBoxes[i][j].Text, out value))
            {
                MessageBox.Show("Invalid entry");
                TextBoxes[i][j].Focus();
                return null;
            }
            values[i][j] = value;
        }
    }

    return values;
}

This method creates a float[][] and then fills it by looping through the rows and columns of the TextBoxes array, which holds references to the program’s TextBox controls.

The following code shows how the program initializes the TextBoxes array.

private TextBox[][] TextBoxes;

// Display the image converted to sepia tone.
private void Form1_Load(object sender, EventArgs e)
{
    TextBoxes = new TextBox[][]
    {
        new TextBox[] {txt00, txt01, txt02, txt03, txt04},
        new TextBox[] {txt10, txt11, txt12, txt13, txt14},
        new TextBox[] {txt20, txt21, txt22, txt23, txt24},
        new TextBox[] {txt30, txt31, txt32, txt33, txt34},
        new TextBox[] {txt40, txt41, txt42, txt43, txt44},
    };
}

The program declares the TextBoxes array at the class level. Its Form_Load event handler initializes the array.

To quickly recap, GetColorMatrix calls GetMatrix to get the float values that you entered and uses the array to create and return a ColorMatrix object. The GetMatrix method loops through the TextBoxes array to fill in the array that it returns.

CopyImage

The .NET Image class provides a couple of ways to create a copy of an image. The least sumbersome is to call its Clone method. Unfortunately that method returns a generic oobject, so you need to convert it into an Image if you want to use it as an Image.

Copying images is something I do a lot, so I decided to create the following two extension methods to make that easier.

public static class Extensions
{
    public static Image CopyImage(this Image image)
    {
        return (Image)image.Clone();
    }

    public static Image CopyImage(this Image image, ImageAttributes attributes)
    {
        Bitmap result = new Bitmap(image.Width, image.Height);
        using (Graphics gr = Graphics.FromImage(result))
        {
            Rectangle rect = new Rectangle(
                0, 0, image.Width, image.Height);
            gr.DrawImage(image, rect,
                0, 0, image.Width, image.Height,
                GraphicsUnit.Pixel, attributes);
        }
        return result;
    }
}

The first method clones the image, casts the result back into an Image, and returns the result. You can use it as in the following code.

Image copy_of_image = original_image.CopyImage();

The second method copies an image while applying an ImageAttributes object to it. That’s how this example colors images.

The second method creates a new Bitmap object that is the same size as the original image and makes an associated Graphics object. It makes a Rectangle sized to fit the image and then calls the Graphics object’s DrawImage method to draw the image onto the new Bitmap. (This is one of the reasons why I wanted to make an extension method. The methods for drawing an image onto another image is just plain awkward. With this method I don’t have to mess with creating a Rectangle and passing the coordinates, width, and height into the DrawImage method.)

The AdjustColor method shown earlier uses this second version of CopyImage in the following statement.

// Make the result image.
return image.CopyImage(attributes);

Conclusion

The example program includes several other details such as code to open and save files, and menu items that fill in matrix values for specific kinds of coloring such as red, green, sepia, and so forth. (The picture at the top of the post shows an image converted to sepia tone.)

Download the example to experiment and to see additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Test graphical transformations in C#

[example]

This example draws several pictures to let you test graphical transformations. The general approach is the same for each picture. The following section explains how the program draws the pictures that demonstrate the various graphical transformations. The sections after that one explain the graphical transformations themselves.

Drawing

The program uses the same method to draw each of the pictures. Here I’ll explain how the program draws the picture that demonstrates translation.

When you change the value in a text box, a TextChanged event handler similar to the following executes.

private void txtTranslate_TextChanged(object sender, EventArgs e)
{
    picTranslate.Refresh();
}

This event handler simply refreshes the appropriate PictureBox to make its Paint event handler execute. The following code shows the event handler for the translation picture.

private void picTranslate_Paint(object sender, PaintEventArgs e)
{
    float dx, dy;
    float.TryParse(txtDx.Text, out dx);
    float.TryParse(txtDy.Text, out dy);
    e.Graphics.TranslateTransform(dx, dy);
    DrawPicture(sender as PictureBox, e.Graphics);
}

This code reads the values that you entered in the text boxes to get the parameters that are appropriate for the current transformation. For the translation picture, the code gets the Dy and Dy values.

Next the code applies a transformation to the event handler’s Graphics object. It then calls the following DrawPicture method.

private void DrawPicture(PictureBox pic, Graphics gr)
{
    gr.Clear(pic.BackColor);
    gr.SmoothingMode = SmoothingMode.AntiAlias;
    DrawSmiley(gr, pic.ClientRectangle);
}

This method clears the current PictureBox. It sets the Graphics object’s SmoothingMode property to produce smooth lines and curves, and then calls the DrawSmiley method.

The DrawSmiley method just uses a series of drawing methods to draw ellipses, arcs, and other shapes to produce the smiley face. The details aren’t important for the discussion of graphical transformations, so I won’t show that method here. Download the example to see the details.

There are two important things that you need to know about the DrawSmiley method.

First, the smiley face is drawn with the origin (0, 0) in the upper left corner. Remember that Y coordinates increase downward in .NET graphics.

Second, notice that the smiley face uses some fairly thick pens. The transformations apply to pens so, depending on the transformation, the curves’ thickness may vary. For example, if you look closely at the Scale picture, you’ll see that the curves are thicker in the X direction than in the Y direction. That’s because the scale transformation is larger in the X direction than in the Y direction.

Drawing the Original

The following code shows how the program draws the original smiley face in the upper left PictureBox.

private void picOriginal_Paint(object sender, PaintEventArgs e)
{
    DrawPicture(sender as PictureBox, e.Graphics);
}

This code simply calls the DrawPicture method described earlier.

Translating

You’ve already see this one.

private void picTranslate_Paint(object sender, PaintEventArgs e)
{
    float dx, dy;
    float.TryParse(txtDx.Text, out dx);
    float.TryParse(txtDy.Text, out dy);
    e.Graphics.TranslateTransform(dx, dy);
    DrawPicture(sender as PictureBox, e.Graphics);
}

To apply a translation transformation, call the Graphic object’s TranslateTransform method passing it the X and Y offsets that you want to use.

Rotating

The following code shows how to rotate a drawing around the origin.

private void picRotate_Paint(object sender, PaintEventArgs e)
{
    float angle;
    float.TryParse(txtRotate.Text, out angle);
    e.Graphics.RotateTransform(angle);
    DrawPicture(sender as PictureBox, e.Graphics);
}

To rotate, call the Graphic object’s RotateTransform method passing it the angle through which you want the drawing rotated. The angle is measured clockwise in degrees.

Scaling

The following code shows how to scale a drawing in the X and Y directions.

private void picScale_Paint(object sender, PaintEventArgs e)
{
float sx, sy;
float.TryParse(txtSx.Text, out sx);
float.TryParse(txtSy.Text, out sy);
try
{
e.Graphics.ScaleTransform(sx, sy);
DrawPicture(sender as PictureBox, e.Graphics);
}
catch { }
}

To scale, call the Graphic object’s ScaleTransform method passing the X and Y scale factors.

This code uses a try catch block because the ScaleTransform method throws an exception if either of the scale factors is zero.

Note that you can use negative scale factors. For example, you could set sx = -1 to flip the drawing horizontally. In this example, that would move the drawing to the left off of the viewable area, so you would probably want to translate it so it is visible again.

Skewing

A skew or shear transformation modifies a pixel’s X coordinate by an amount that depends on its Y coordinate and vice versa. For example, suppose the X skew factor is 2. Then the point (a, b) is mapped to the new point (2 * a * y, b).

it’s not obvious exactly how general skew transformations work or why you might want to use one. You can use them to do things like rotate an image (but the RotateTransform method is easier) or perform certain kinds of projections, but they’re generally relatively confusing.

The following code shows how the program demonstrates the skew transformation.

private void picSkew_Paint(object sender, PaintEventArgs e)
{
    float sx, sy;
    float.TryParse(txtSkewX.Text, out sx);
    float.TryParse(txtSkewY.Text, out sy);

    Matrix matrix = new Matrix();
    matrix.Shear(sx, sy);

    e.Graphics.Transform = matrix;
    DrawPicture(sender as PictureBox, e.Graphics);
}

The Graphics object does not have a skewing method, probably because Microsoft didn’t think people would use it very often. (I can’t disagree.)

To use a skewing transformation, create a Matrix object and calls its Shear methopd, passing it the X and Y shew factors. Then set the Graphics object’s Transform property equal to that matrix.

The Matrix class provides several other extremely useful methods for performing graphical transformations. The following list summarizes some of the most useful.

  • Rotate – Rotates the drawing around the origin
  • RotateAt – Rotates the drawing around any point, not just the origin
  • Scale – Scales in the X and Y directions
  • Shear – Applies a skew or shear transformation
  • Translate – Translates in the X and Y directions

If you only want to use one or two graphical transformations, the Graphics object’s methods are usually easier, but sometimes the Matrix class can be helpful.

Using General Transformations

The Matrix class also lets you set some, but not all, of its elements directly. One of its constructors allows you to pass six floating point values in to assign values to the matrix’s left two columns.

If you enter values into the Elements example’s text boxes, the program uses the following code to place those values in a Matrix and then use the result to transform the drawing.

private void picElements_Paint(object sender, PaintEventArgs e)
{
    float sx = 1;
    float sky = 0;
    float skx = 0;
    float sy = 1;
    float dx = 0;
    float dy = 0;
    float.TryParse(txtM11.Text, out sx);
    float.TryParse(txtM12.Text, out sky);
    float.TryParse(txtM21.Text, out skx);
    float.TryParse(txtM22.Text, out sy);
    float.TryParse(txtMdx.Text, out dx);
    float.TryParse(txtMdy.Text, out dy);

    try
    {
        Matrix matrix = new Matrix(sx, sky, skx, sy, dx, dy);
        e.Graphics.Transform = matrix;
        DrawPicture(sender as PictureBox, e.Graphics);
    }
    catch { }
}

This code simply parses the values that you entered and then passes them into the Matrix class’s constructor.

[example]

The constructor’s parameter have the non-intuitive names m11, m12, m21, m22, dx, and dy. The picture on the right shows where those values go in the matrix.

The program saves the values in variables that have slightly more meaningful names. The following list describes the effects those variables have on the transformation.

    sx – X scale factor
    sy – Y scale factor
    skx – X skew factor
    sky – Y skew factor
    dx – X translation
    dy – Y translation

[example]

You can use the scale values by themselves relatively easily. Similarly if you use only the skew values, the result is easy to understand. (Or as easy as skewing ever is.)

The translation values move the resulting drawing after any scaling or skewing has happened, so they are also easy to understand, even if you scale or skew the drawing.

However, the scale and skew values together interact in non-obvious ways. For example, a rotation uses a specific combination of scale and skew values. The picture on the right shows values that cause rotation by 15 degrees followed by translation of 20 pixels in the X direction and 10 pixels in the Y direction.

You can experiment with the array elements if you like.

As far as I know, there is no way to assign values to a Matrix object’s rightmost column. They are assumed to have the values 0, 0, and 1 to make using these objects as transformations easier. That also means you cannot use this class to represent a mathematical matrix, only the kind of matrix used by transformations.

The Matrix class has some other constructors that let you map a source rectangle to a destination parallelogram. It’s often easier to use those constructors to perform a complex transformation instead of using a combination of scalings, rotations, and translations.

Combining Transformations

Whenever you call one of the Graphics class’s graphical transformation methods, the transformation is added to any previously applied transformation. For example, you could use multiple transformation methods to translate an object, rotate it, and then scale the result.

Note that the result of multiple transformations depends on the order in which you perform them. In general, a rotation followed by a translation is not the same as a translation followed by a rotation.

Each of the Graphics class’s graphical transformation methods takes an optional parameter that indicates whether you want the new transformation applied before or after any previously applied transformations. For example, the following statement adds a rotation after any mpreviously applied transformations.

e.Graphics.RotateTransform(45, MatrixOrder.Append);

It is important to note that the default is MatrixOrder.Prepend! That means if you omit this parameter, then the new transformation is applied before the existing transformations. That sort of makes sense from a mathematical point of view and there are certain kinds of programs where that makes sense, but it’s counterintuitive and a common source of bugs in graphics programs.

The Graphics class has three other methods that you may find useful when working with transformations: Save, ResetTransform, and Restore.

The Save method returns a GraphicsState object that represents the Graphics object’s current state including its transformations. The Reset method resets the Graphics object and removes any transformations. The Restore method restores a Graphics object to the state it had when you called Save.

For example, suppose your program draws several objects with different transformations. To draw an object you could (1) save the current state, (2) reset the Graphics object and apply the current object’s transformations, and finally (3) restore the original state. That way you don’t need to worry about messing up any previous transformations when you draw the new object.

Drawing Untransformed Lines

As I mentioned earlier, transformations apply to pens. If you scale a drawing in the X and Y directions, the thickness of drawn lines and curves will also be scaled. There are a few ways you can handle this.

First, you can just ignore it and get on with life. This is a good option because it’s easy.

Second, a pen with zero width is not scaled. That includes the standard pens such as Pens.Red and Pens.Chartreuse.

Sometimes, however, you may want to draw a thick line that is not scaled. For example, suppose you are drawing a graph. You could use graphics transformations to make the graph fit the drawwing area nicely, but you then want to draw some think lines. You can use the Matrix class to do that.

Create a Matrix object and use its methods to define the transformation that you want to use. Like the Graphics class, the Matrix class’s graphical transformation methods include an optional parameter that lets you determine the order in which they are applied. (You can also multiply Matrix objects together to combine their transformations if that is more convenient.)

Now you can set the Graphics object’s Transform property equal to the Matrix.

To draw a shape with an untransformed pen, use the Matrix object’s TransformPoints method to transform the points that define the shape. Then reset the Graphics object and draw the shape using the transformed points. Now the Graphics object uses the identity transformation so the pen is not distorted.

Before I finish, I’ll tell you one more handy technique that uses the transformation Matrix. You can copy the matrix into a new Matrix object and then call the new object’s Invert method. Mathematrically that inverts the matrix. Geometrically it also reverses the graphical transformations that the Matrix represents. If the original Matrix represents translation by <-2, 4> followed by a rotation through 30 degrees, then the inverse represents rotation by -30 degrees followed by translation by <2, -4>. In other words, if you apply the first matrix followed by its inverse, then you leave points unchanged.

What this also means is that the inverted matrix can translate from points on the final drawing back to the original coordinate system where you drew the picture.

For a concrete example, suppose you use scaling and translation to make a graph fit nicely on a PictureBox. For example, suppose the graph’s X and Y coordinates like in the ranges 2000 ≤ x ≤ 2020, 0 ≤ y ≤ 100. By using a transformation, you can draw in those coordinates and have the result map to a particular area on the PictureBox.

Now if the user clicks on a point on the PictureBox, you can use the inverted transformation matrix to map that point back to the graph’s coordinate system. The PictureBox gives you a point on its surface, and the inverted transformation maps that point back to the graph’s coordinates 2000 ≤ x ≤ 2020, 0 ≤ y ≤ 100. Now you can easily tell which part of the graph the user clicked, and you can display a tooltip or display other relevant data.

Conclusion

This isn’t the end of the story of transformations. You can read more about them in my book WPF 3d, Three-Dimensional Graphics with WPF and C#. As the title implies, that book focuses on three-dimensional graphics, but transformation concepts apply in two-, three-, or even higher-dimensional graphics. (Although higher-dimensional graphics is pretty esoteric.)

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


Download Example   Follow me on Twitter   RSS feed   Donate




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

Use VBA code to pick random cells from a filtered selection in an Excel workbook

pick random elements

This example shows how you can use VBA code to pick random cells in an Excel workbook when the selected cells are filtered.

The post Use VBA code to pick random cells from the currently selected cells in an Excel workbook shows how you can pick random cells from a selection. Unfortunately that method only works if the data is unfiltered. If you add a filter, then the method works but it includes rows that are filtered out.

This post shows how to pick random cells while excluding those that are filtered out.

Picking Items

The original method looped used Application.Selection to look at the selected cells. Unfortunately that method includes cells that should be filtered out.

You can consider only the non-filtered cells by looking at Application.Selection.SpecialCells(xlCellTypeVisible). That produces a range that includes only the selected cells. Unfortunately if you then look at this range’s cells as in Application.Selection.SpecialCells(xlCellTypeVisible).Cells(1), the range again includes cells that should be filtered out.

The problem is that the range’s Cells property treats the range as a contiguous area and does not exclude filtered out rows.

This example loops through the selected cells and copies them into an array. It then randomizes the array and selects the correct number of them from the array.

The most interesting piece of code is the following SelectRandom method.

' Select the indicated number of items from the
' currently selected cells.
Public Sub SelectRandom(ByVal num_to_select As Integer)
Dim num_items As Integer
Dim i As Integer
Dim j As Integer
Dim selected_range As Range
Dim temp As Range
Dim ranges() As Range

    ' Make sure the selection is a range.
    If Not (TypeOf Application.Selection Is Range) Then
        MsgBox "The current selection is not a range."
        Exit Sub
    End If

    ' Make sure we're selecting at least 1 item.
    If num_to_select < 1 Then
        MsgBox "Cannot pick fewer than 1 item."
        Exit Sub
    End If

    ' See how many items are selected.
    Set selected_range = Application.Selection.SpecialCells(xlCellTypeVisible)
    num_items = selected_range.Count
    If num_to_select > num_items Then
        MsgBox "You cannot pick more items than there are in total."
        Exit Sub
    End If

    ' Make an array containing the selected visible cells.
    ReDim ranges(0 To num_items - 1)
    i = 0
    For Each temp In selected_range
        Set ranges(i) = temp
        i = i + 1
    Next temp

    ' Randomize the cells.
    For i = num_items - 1 To 1 Step -1
        ' Randomly pick an index at or below this one.
        j = Int((i + 1) * Rnd)
        
        ' Swap ranges(j) and ranges(i).
        Set temp = ranges(i)
        Set ranges(i) = ranges(j)
        Set ranges(j) = temp
    Next i
    
    ' Deselect all items.
    Application.Selection.Font.Bold = False
    Application.Selection.Font.Color = vbBlack

    ' Select the first items.
    For i = 0 To num_to_select - 1
        Debug.Print ranges(i)
        ranges(i).Font.Bold = True
        ranges(i).Font.Color = vbRed
    Next i
End Sub

This method does some error checking and then creates a range named selected_range that holds the selected cells that are not filtered out. It then makes an array of Range and loops through selected_range copying the selected cells into the array.

Next the code randomizes the values in the array.

The program then makes all of the cells in the original selection use black, non-bold text. It then loops through the desired number of cells in the randomized array and makes those cells bold and red to indicate that they are selected.

Picking From Disconnected Ranges

The original post mentioned above picks random cells from a single contiguous block of cells. If you try to use that method to pick cells from a selection that includes multiple disjoint areas, the method fails.

The post Use VBA to randomize Excel selections avoids that problem by making an array holding the values in all of the cells in the selected range. It randomizes those values and then copies them back into the selected cells.

This post also works with the individual values in the selected range, so it also avoids the disconnected selection problem. That means the code can select random items from a selection that includes multiple disconnected regions with no additional code.

Conclusion

Download the example to try it and to see additional details. For example, add some values in the second column, control-click to select disconnected cells, and click the button to make random selections.


Download Example   Follow me on Twitter   RSS feed




Posted in Excel, VBA | Tagged , , , , , , , , , , | Leave a comment

Make multi-image icons from files in C#

[multi-image icons]

The post Make multi-image icon files in C# showed how to create several bitmaps at runtime and then used them to create multi-image icons in C#. This example lets you load multiple saved image files and use them to create multi-image icons.

Adding Files

This program displays files in a ListView control named lvwFiles. It stores preview images of the image files that you load in a ImageList component named imlFiles. When the program starts, it uses the following code to set some ListView properties.

private void Form1_Load(object sender, EventArgs e)
{
    lvwFiles.LargeImageList = imlFiles;
    lvwFiles.View = View.Tile;
    imlFiles.ImageSize = new Size(64, 64);
}

This code makes the ListView control use the imlFiles ImageList for its large image list. It also sets the ListView control’s View property to Tile so the control displays item text with large images.

The code also sets the ImageList component’s Size property to 64 pixels by 64 pixels. If you add an image to this control, it will resize the image if necessary so it can store a 64 x 64 pixel image.

You could set all of these properties at design time in the Form Designer. I put the code here to make it more obvious how these properties are set.

When you select File menu’s Open command, the following code executes.

private void mnuFileOpen_Click(object sender, EventArgs e)
{
    if (ofdImages.ShowDialog() == DialogResult.OK)
    {
        foreach (string filename in ofdImages.FileNames)
        {
            Bitmap bm = new Bitmap(filename);
            imlFiles.Images.Add(bm);
            lvwFiles.Items.Add(filename, imlFiles.Images.Count - 1);
        }
        mnuFileSaveAs.Enabled = (lvwFiles.Items.Count > 0);
    }
}

This code displays the ofdImages Open File Dialog. Note that I set this control’s Multiselect property to true at design time so it lets you pick multiple files.

If you use the dialog to select one or more files and then click Open, the code loops through the dialog’s FileNames list to see which files you have selected.
For each selected file, the program uses the file to create a Bitmap and adds the Bitmap to the ImageList component. (Remember that the ImageList resizes the Bitmap if necessary so it is 64 x 64 pixels in size.) The code also adds the selected file’s name to the lvwFiles ListView control and sets that item’s image index to the index of the last image in the ImageList component. That makes the ListView display the file’s name (probably truncated) and the image that it contains.

After it has loaded the image files that you selected, the program enables the File menu’s Save As command if you have loaded at least one file.

Removing Files

The program lets you remove files from the ListView control in two ways. First, if you select the Items menu’s Clear command, the following code executes.

private void mnuItemsClear_Click(object sender, EventArgs e)
{
    lvwFiles.Items.Clear();
    imlFiles.Images.Clear();
    mnuFileSaveAs.Enabled = (lvwFiles.Items.Count > 0);
}

This code clears the ListView control and removes all of the images from the ImageList component. It then disables the File menu’s Save As command.

The second way you can remove files is to select one in the ListView control and then press Delete. When you press a key while the ListView has the input focus (and it always does because it’s the only control on the form), the following code executes.

private void lvwFiles_KeyDown(object sender, KeyEventArgs e)
{
    if (e.KeyCode == Keys.Delete)
    {
        foreach (ListViewItem item in lvwFiles.SelectedItems)
        {
            item.Remove();
        }

        if (lvwFiles.Items.Count == 0)
        {
            mnuFileSaveAs.Enabled = false;
            imlFiles.Images.Clear();
        }
    }
}

If the key you pressed was Delete, the code loops through the ListView control’s selected items and removes them.

When it has finished removing items, the code checks whether the ListView is empty. If the ListView contains no items, then the code disables the File menu’s Save As command and empties the ImageList.

Notice that the code does not remove any images from the ImageList unless the ListView is completely empty. If you remove an item’s image from the list, then the other items’ ImageIndex properties are not automatically renumbered so they may point to the wrong images. All of this means that the ImageList may contain images that are no longer used, but that won’t be a problem unless you add and then remove a huge number of images. Don’t do that.

Saving the Icon File

If you have one or more images loaded and you select the File menu’s Save As command, the following code executes.

private void mnuFileSaveAs_Click(object sender, EventArgs e)
{
    if (sfdIcon.ShowDialog() == DialogResult.OK)
    {
        List<Bitmap> bitmaps = new List<Bitmap>();
        foreach (ListViewItem item in lvwFiles.Items)
        {
            bitmaps.Add(new Bitmap(item.Text));
        }

        IconFactory.SavePngsAsIcon(
            bitmaps, sfdIcon.FileName, true);
        this.Icon = new Icon(sfdIcon.FileName);
    }
}

This code displays a Save File Dialog. If you select an icon file and click Save, the program creates a list named bitmaps to hold Bitmap objects.

The program then loops through the items in the ListView control. For each item, the program uses the file name stored in the item to create a Bitmap and adds the Bitmap to the bitmaps list.

Notice that the code does not use the images stored in the ImageList. Remember that the ImageList resizes those images so they are all 64 x 64 pixels. If you put those images in the bitmaps list, then the resulting icon would contain only images with that size.

When it has finished building the bitmaps list, the program calls the IconFactory.SavePngAsIcon method described in the previous post. See that post for details about how the method works.

The event handler finishes by making the program’s form use the newly created icon loaded from the icon file. Now Windows will use the images stored in the icon resizing them as needed if a particular size is not contained in the icon. For example, look at the picture at the top of this post. The left end of form’s title bar displays a 16 x 16 pixel image. The icon that I created contained 32 x 32, 64 x 64, and 128 x 128 pixel images but no 16 x 16 pixel image. To create the 16 x 16 pixel image that it needed, Windows resized the 32 x 32 pixel image.

If you press Alt+Tab or Win+Tab, Windows will display the appropriate (possibly resized) image from the form’s icon.

In this example I used three PNG files that display their sizes so it’s easy to tell which image is being displayed. In a real icon file, you would probably want the different image files to contain similar images.

Application Icons

The code shown in the preceding section shows how a program can set a form’s icon at runtime. Of course you can also set the form’s icon at design time by using the From Designer. However, that doesn’t set the application’s icon. If you look at the compiled executable in File Explorer, you’ll see that it displays a default icon.

To set the application’s icon, open Visual Studio’s Project menu and select Properties. On the Application tab, click the ellipses next to the icon file text box as shown in the following picture.


[multi-image icons]

Select the icon file that you want the executable to display and click Open.

Now the executable will display images from the file you selected. For example, drag the executable around or use File Explorer’s different views to see different icon sizes.

Conclusion

The previous post shows how you can make multi-image icons. This example shows how you can use the methods described in that post to create icons from bitmap files that you can create using MS Paint or any other drawing program.

Download the example program to create your own icon files and to see additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Make multi-image icon files in C#

[icon files]

The most interesting thing about icon files is that they can contain several images with different sizes. When a program needs to display the icon, it can pick the size that is appropriate. For example, Windows 10 displays icons in various sizes including 16 x 16, 32 x 32, 48 x 48, and 256 x 256 pixels. It can even scale some icons to produce other sizes. And in “Classic Mode” Windows displays icons with sizes 16 x 16, 24 x 24, 32 x 32, 48 x 48 and 64 x 64 pixels.

The following section explains a basic approach for building multi-image icon files. The section after that describes some changes that I made to the basic approach.

IconFactory

A C# program can save an Icon object into an icon file, but it does not have a simple way to create multi-image icon files.

Fortunately an unnamed poster on Stack Overflow posted an IconFactory class that lets you create multi-image icons. The post is at How to create an Icon file that contains Multiple Sizes / Images in C#.

The basic approach is straightforward, just a lot of work. What this user did was study the specifications for icon files and then write binary data in that format.

Here’s the core of the Stack Overflow IconFactory class.

// Write an icon into a stream.
// Note that this closes the stream.
public static void SavePngsAsIcon(
    IEnumerable<Bitmap> images,
    Stream stream, bool notify_system)
{
    Bitmap[] ordered_images =
        images.OrderBy(i => i.Width).ThenBy(i => i.Height).ToArray();
    using (BinaryWriter writer = new BinaryWriter(stream))
    {
        writer.Write(HeaderReserved);
        writer.Write(HeaderIconType);
        writer.Write((ushort)ordered_images.Length);

        Dictionary<uint, byte[]> buffers =
            new Dictionary<uint, byte[]>();
        uint length_sum = 0;
        uint base_offset =
            (uint)(HeaderLength * EntryLength * ordered_images.Length);
        for (int i = 0; i < ordered_images.Length; i++)
        {
            Bitmap image = ordered_images[i];
            byte[] buffer = CreateImageBuffer(image);
            uint offset = base_offset + length_sum;

            writer.Write(GetIconWidth(image));
            writer.Write(GetIconHeight(image));
            writer.Write(PngColorsInPalette);
            writer.Write(EntryReserved);
            writer.Write(PngColorPlanes);
            writer.Write((ushort)Image.GetPixelFormatSize(
                image.PixelFormat));
            writer.Write((uint)buffer.Length);
            writer.Write(offset);

            length_sum += (uint)buffer.Length;
            buffers.Add(offset, buffer);
        }

        foreach (KeyValuePair<uint, byte[]> kvp in buffers)
        {
            writer.BaseStream.Seek(kvp.Key, SeekOrigin.Begin);
            writer.Write(kvp.Value);
        }
    }

    if (notify_system)
    {
        SHChangeNotify(
            HChangeNotifyEventID.SHCNE_ASSOCCHANGED,
            HChangeNotifyFlags.SHCNF_IDLIST,
            IntPtr.Zero, IntPtr.Zero);
    }
}

This method takes as a parameter an IEnumerable of bitmaps. It uses LINQ to sort the images by their sizes, first by width and then by height.

The code then creates a BinaryWriter to write results into the stream passed into the method. It writes some header information into the writer and then loops through the bitmaps. The method writes each bitmap’s width, height, and other data into the stream.

One of the more interesting pieces of information that the program writes into the stream is the bitmap’s image buffer. The code gets that from the CreateImageBuffer method, which I’ll describe shortly. That buffer contains the pixel information that defines the bitmap.

Note that the BinaryWriter closes the original stream when its using block ends. You’ll see why this matters later.

After the method has finished writing the icon data into the stream, it checks its notify_system parameter and calls SHChangeNotify if it is true. I’ll say more about that in the next section.

The following code shows the CreateImageBuffer method.

private static byte[] CreateImageBuffer(Bitmap image)
{
    using (MemoryStream stream = new MemoryStream())
    {
        image.Save(stream, ImageFormat.Png);
        return stream.ToArray();
    }
}

This method creates a MemoryStream object to hold data in memory. It then calls the bitmap’s Save method to make it save itself into the stream. The method returns the stream converted into an array. That becomes the buffer that the SavePngsAsIcon method uses to represent the bitmap’s pixel data.

Changes

That’s the basic approach, but I made a few changes to the original version posted on Stack Overflow.

First, I reformatted some of the code to make it easier to read. For example, the original version qualified method calls with the IconFactory class name even though they weren’t really necessary. For example, the code used IconFactory.HeaderReserved even though only HeaderReserved was necessary.

I also removed the error handling code that was in the original post. Not that error handling is unimportant, but I wanted to make the example easier to understand. You should definitely include error handling in anym program.

Some of the original error handling also seemed a bit excessive. For example, if you try to pass a null array of bitmaps into the original version, the code throws an appropriate exception. If you do that with this version of the class, you’ll get a “Value cannot be null” exception. It may be a little harder to track down exactly which value is null, but as I said I wanted to keep the example simpler.

The original version of the CreateImageBuffer method called the bitmap’s Save method to save the image in its raw data format. (See the Stack Overflow post to see that.) That only worked with bitmaps loaded from files not created at runtime, so I changed it to ask the bitmap to save itself in PNG format.

My example program saves icon files. Windows caches the images that it displays for icon files, so if you overwrite an existing file with a file that contains new images, Windows does not change the images that you see in places like the desktop and File Explorer. That made testing the program more confusing.

To handle this, I added the notify_system parameter and the call to the SHChangeNotify API function. The new version of the SavePngsAsIcon method uses that function to tell Windows that an icon has changed so it should reload its cache. That makes it display the images that it should for the modified icon file.

If your program is using the SavePngsAsIcon method to create multi-image icons that it uses at runtime, then you should set the notify_system parameter to false so Windows doesn’t need to rebuild its cache. If you save icons in icon files, then set thehwo parameter to true so Windows can update appropriately.

The last major change I made to the IconFactory class is to add two new overloaded versions of the SavePngsAsIcon method. The following code shows the first.

// Save an icon into a file.
public static void SavePngsAsIcon(
    IEnumerable<Bitmap> images,
    string filename, bool notify_system)
{
    using (FileStream stream = new FileStream(filename, FileMode.Create))
    {
        SavePngsAsIcon(images, stream, notify_system);
    }

}

This version takes as a parameter a file name. It creates a FileStream to create that file and then uses the previous version of SavePngsAsIcon to write the icon into the FileStream. That saves the icon into the indicated file.

The following code shows the second overloaded version of the SavePngsAsIcon method, which creates an icon an returns it as an Icon object.

// Return an icon as an Icon object.
public static Icon SavePngsAsIcon(
    IEnumerable<Bitmap> images)
{
    using (MemoryStream stream = new MemoryStream())
    {
        // Write the icon into the stream.
        SavePngsAsIcon(images, stream, false);

        // Create a new stream from the first one.
        using (MemoryStream stream2 =
            new MemoryStream(stream.ToArray()))
        {

            // Create and return an icon from the stream.
            return new Icon(stream2);
        }
    }
}

This code creates a MemoryStream and calls the first version of SavePngsAsIcon to write the icon into the stream. As I mentioned earlier, SavePngsAsIcon closes the stream, so we can’t just rewind it and use it as a stream. Instead this code creates a new stream, initializing it from the first one. It then uses that stream to create a new Icon and returns that Icon.

The Main Program

When the example program starts, it executes the following Load event handler.

private void Form1_Load(object sender, EventArgs e)
{
    Bitmap bm16 = MakeBitmap16();
    Bitmap bm24 = MakeBitmap24();
    Bitmap bm32 = MakeBitmap32();
    Bitmap bm48 = MakeBitmap48();
    Bitmap bm256 = MakeBitmap256();

    pic16.Image = bm16;
    pic24.Image = bm24;
    pic32.Image = bm32;
    pic48.Image = bm48;
    pic256.Image = bm256;

    Bitmap[] bitmaps = { bm16, bm32, bm24, bm48, bm256 };

    // Save the icon into a file.
    string filename = "result.ico";
    IconFactory.SavePngsAsIcon(bitmaps, filename, true);

    // Make this form use the icon.
    this.Icon = IconFactory.SavePngsAsIcon(bitmaps);
}

This code calls various MakeBitmapXx methods to create bitmaps in various sizes. Those methods are relatively straightforward so I won’t show them here. Download the example to see how they work.

The code then displays those bitmaps in a group of PictureBox controls. If you look closely at the picture at the top of this post, you’ll see that the different images are all arranged differently. That’s common for icons in various sizes. What looks good at one size would look bad at another. For example, if you simply shrank the 256 x 256 pixel icon to the 16 x 16 pixel size, you wouldn’t be able to read any of the text.

After it displays the bitmaps, the code makes an array containing them and passes them into IconFactory.SavePngsAsIcon. It passes that method a file name so the method creates the icon and saves it into the file result.ico. You can find the file in the bin/Debug subdirectory. File Explorer displays the file using an appropriate image depending on the View setting. (Details, Large icons, Extra large icons, etc.) If you drag the file around, Windows will display a 96 x 96 pixel version that it creates by scaling the 256 x 256 pixel image.

Finally, the event handler calls another overloaded version of SavePngsAsIcon to create an Icon object and makes the form use that icon. You can see the small verison of the icon in the picture at the top of this post.

[icon files]

If you use Alt+Tab or Win+Tab, you’ll see the 24 x 24 pixel version of the icon above and to the left of the program’s image as shown in the picture on the right.

Conclusion

C# does not provide a way to make multi-image icon files, but the IconFactory class does. Its overloaded versions of the SavePngsAsIcon method let you create an icon from a set of bitmaps and save the result into a stream, file, or Icon object. Download the example to experiment with it and to see the many additional details.

(Many thanks to the unnamed Stack Overflow user who did the research necessary to create the original version of the IconFactory class.)


Download Example   Follow me on Twitter   RSS feed   Donate




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

Use command line arguments to open files in C#


[command line arguments]

Whenever a C# program starts, it receives command line arguments. You can use those arguments to let your program open files that are associated with the program.

For example, when you double-click a .doc or .docx file, Microsoft Word opens and displays the file. You can use command line arguments to do something similar with your programs. Before you learn how to do that, you should know how you can send arguments to the program. (If you already know how you can send one or more files to a program, skip down to the next section.)

Opening With Arguments

Whenever a C# program starts, it receives command line arguments. The first argument is always the name of the program that is executing. Any remaining arguments depend on how you start the program. There are several ways that you can pass command line arguments to the program interactively.

If you double-click on the program, it the program’s name is the only argument that the program receives.

If you open a command window, you can type the program’s name followed by any arguments that you want to pass to the program. This is the most obvious but least convenient way to send arguments to the program.

If you drag-and-drop one or more files onto the program, then the program receives the files’ names as parameters.

In a similar technique, suppose you select one or more files in File Explorer. Then if you right-click the files, you can select Open With to see a sub-menu listing programs that the system thinks might be able to open the file. If you click Choose Another App, then you get a popup listing some apps that you might be able to use. Note that this popup also has a checkbox labeled “Always use this app to open .XXX files.” More on this later.

If you scroll to the bottom of the popup dialog, you’ll find a More Apps choice. (For some reason, Microsoft has made this option pale blue on a white background so it’s hard to see. Maybe it’s different if you’re using a different color scheme.) If you click this, you’ll get (you guessed it) more apps! At the bottom of the new list, there’s yet another pale blue option: Look for another app on this PC. If you get this far without growing bored and wandering away, you get a file selection dialog that lets you pick your program. If you select the program and click Open, then File Explorer opens your program and sends it (as command line arguments) the names of the files that you originally right-clicked so long ago.

Here’s a summary of this process.

  1. Select one or more files.
  2. Right-click.
  3. Select Open With > Choose Another App.
  4. Check the “Always use this app to open .XXX files” box if you like, scroll to the bottom, and select More Apps.
  5. Scroll to the bottom and select “Look for another app on this PC.”
  6. Use the file selection dialog to find the executable program, select it, and click Open.

Whew!

If you go through that whole process and you check the “Always use this app to open .XXX files” box, then later you can double-click on a .XXX file to open the program. In that case, the name of the file that you double-clicked is passed to the program as a command-line argument.

If you followed the process and checked the box, you can also select one or more files, right-click them, and select Open. Again your program starts and it receives the file name(s) in the command line arguments.

If you place a shortcut to your program in the SendTo directory, then you can right-click files and use SendTo to send them to your program.

If you create a shortcut to your program, you can right-click the shortcut, select Properties, and enter command line arguments in the Target text box after the file’s name.

There are probably many other methods for interactively launching a program with command line arguments. There are also some non-interactive methods. For example, you can use the System.Diagnostics.Process class to start a process. You can set the object’s StartInfo.Arguments property before you call the Start method to pass the program command line arguments.

There are probably many other ways to start programs interactively or non-interactively, but this is more than enough for now. WAY more than enough! There’s just one more way to do this that you really need to know about.

Command Line Arguments in Visual Studio

You can pass command line arguments to your program when you run it in Visual Studio. That’s important because that’s the easiest way to test and debug how your program handles those arguments.

To set the arguments, open the Project menu and select Properties. On the Properties window, select the Debug tab and enter values in the Command Line Arguments text box as shown in the following picture.


[command line arguments]

Separate the values with spaces. If a value should contain a space, enclose the value in quotes.

Now when you start the program inside Visual Studio, it will receive the command line arguments that you entered.

Loading Files

That’s enough ways to send arguments to the program. It’s time to talk about your program.

The program should be able to open some kind of file. It could be a serialization of some sort, a data file with a special format, whatever.

This example loads drawing serializations. It extends the example Save and restore line drawings in C#. The original program’s File > Open menu item loaded a file containing lines. The new version of the program moves the code that loads the file into the following method.

// Open the file.
private void LoadFile(string filename)
{
    try
    {
        XmlSerializer xml_serializer = new XmlSerializer(TheDrawing.GetType());
        using (FileStream file_stream = new FileStream(filename, FileMode.Open))
        {
            Drawing new_drawing =
                (Drawing)xml_serializer.Deserialize(file_stream);
            TheDrawing = new_drawing;
            picCanvas.Refresh();
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

This code creates a serializer to work with the class named Drawing. The object TheDrawing is of that type. The program opens a file stream to the drawing file, deserializes the stream into a new Drawing object, saves the drawing in the variable TheDrawing, and redraws the program’s PictureBox to show the lines.

Now the File > Open menu command displays an open file dialog and calls the LoadFile method to load the selected file.

Your program may do something different, but it will be easier if you have a method similar to the one above that loads a file.

Processing Command Line Arguments

When the program starts, the following Load event handler executes to handle the command line arguments.

private void Form1_Load(object sender, EventArgs e)
{
    string[] args = Environment.GetCommandLineArgs();
    if (args.Length < 2)
    {
        // We have no command line arguments.
        // Do nothing.
        return;
    }

    // Open the first file.
    MessageBox.Show("Opening [" + args[1] + "]");
    LoadFile(args[1]);

    // If we have more than one file name,
    // load them in their own instances.
    for (int i = 2; i < args.Length; i++)
    {
        // See: Open a file with the system’s default application in C#
        // http://csharphelper.com/blog/2014/08/open-a-file-with-the-systems-default-application-in-c/
        try
        {
            MessageBox.Show("Spawning [" + args[i] + "]");
            System.Diagnostics.Process.Start(args[0], args[i]);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }
}

This code first checks the length of the command argument array. If the array contains a single value, then it is the program’s name. In that case the program does not need to load any files so the event handler simply returns.

Next the code displays a message to let you know what it is doing and then calls the LoadFile method, passing it the first command line argument. That argument will be the name of the first file passed to the program.

The code then loops through any remaining command line arguments. For each argument the code displays a message box and uses System.Diagnostics.Process.Start to execute the program, which has the path stored in args[0]. It passes the value in args[i] in as a command line argument. When the new instance of the program starts, it has a single parameter so the program just loads it.

Now if you somehow pass multiple files to the program, the program loads the first one and launches copies of itself to load the others.

(After you have your program working correctly, remove the message boxes that tell you what the program is doing.)

Conclusion

It’s not too hard to make a program load multiple files when it starts. It checks its command line arguments and loads the file names in args[1]. Then if there are more arguments, the program can launch copies of itself to handle the other files.

Download the example program and give it a try. Try selecting the sample files test.lines, test2.lines, and test3.lines and then dragging them onto the executable program.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in serialization, system | Tagged , , , , , , , | Leave a comment

Make a digital clock in C#


[digital clock]

This example uses the techniques described in the example Draw LED-style letters in C# to draw a digital clock. That earlier example showed one way to draw LED-style letters. This example simply uses the LedText class from that post to draw the digital clock.

Drawing Colons

If you look at a real digital clock, you’ll see that it has several special-purpose LEDs. Most digital clocks use special LEDs to draw the colon between the hours, minutes, and seconds. Those positions are always colons, so they don’t need to hold a bunch of LEDs that you can turn on and off. Instead they simply hold two LEDs that are always bright. Some clocks also have periods, slashes, or other LEDs that never change. For this example I decided to only worry about the colons.

Because the colon (and other punctuation symbols) don’t need to use a bunch of LEDs, they are relatively thin. You could make a more flexible model of LED letters to include these special characters, but I decided to just make a method to draw LEDs.

The new example adds the following DrawColon method to the LedText class.

// Draw a colon.
public void DrawColon(Graphics gr,
    Brush bg_brush, Brush used_brush,
    Pen used_pen, Brush unused_brush,
    Pen unused_pen, PointF position)
{
    // Clear the background.
    gr.FillRectangle(bg_brush,
        position.X, position.Y,
        LedThickness, CellHeight);

    float y1 = position.Y + CellHeight / 4f;
    float y2 = y1 + CellHeight / 2f;

    RectangleF rect1 = new RectangleF(
        position.X, y1 - LedThickness / 2f,
        LedThickness, LedThickness);
    gr.FillRectangle(used_brush, rect1);
    gr.DrawRectangle(used_pen, rect1);

    RectangleF rect2 = new RectangleF(
        position.X, y2 - LedThickness / 2f,
        LedThickness, LedThickness);
    gr.FillRectangle(used_brush, rect2);
    gr.DrawRectangle(used_pen, rect2);
}

This method simply draws two rectangles that are as thick as the object’s LedThickness value and positioned 1/4 and 3/4 of the way down from the top of the letter’s cell.

Note that the Graphics class has a FillRectangle method that fills a RectangleF, but it does not have a corresponding DrawRectangle method to draw a RectangleF. To make drawing the rectangles easier, I created the following extension method.

public static class Extensions
{
    public static void DrawRectangle(this Graphics gr,
        Pen pen, RectangleF rect)
    {
        gr.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height);
    }
}

The LedText class needs only one other change to handle the colon. That change is shown in the following DrawText method.

// Draw a sequence of letters.
public void DrawText(Graphics gr, Brush bg_brush,
    Brush used_brush, Pen used_pen,
    Brush unused_brush, Pen unused_pen,
    PointF position, float h_spacing, string text)
{
    float cell_space = CellWidth * (h_spacing - 1);
    foreach (char ch in text)
    {
        if (ch == ':')
        {
            DrawColon(gr, bg_brush, used_brush, used_pen,
                unused_brush, unused_pen, position);
            position.X += LedThickness;
        }
        else
        {
            DrawLetter(gr, bg_brush, used_brush, used_pen,
                unused_brush, unused_pen, position, ch);
            position.X += CellWidth;
        }
        position.X += cell_space;
    }
}

This method first calculates the amount of horizontal space it should add between letters. The h_spacing value indicates the amount by which the cell width should be multiplied to move to the next letter. For example, this value might be 1.2. The method finds the amount of space between cells by multiplying this value by the cell width and then subtracting the cell width.

The code then loops through the text’s letters as before. This time if the letter is a colon, the method calls the DrawColon method and adds the width of the colon, which is LedThickness. If the letter is not a color, then the method calls the DrawLetter method as before and adds the letter’s width, which is CellWidth. In either case the code then adds the space between letters cell_space.

Drawing the Clock

When the program starts, the following code prepares the LedText objects that the program uses to draw the clock.

private LedText TimeLedText, DateLedText;
private void Form1_Load(object sender, EventArgs e)
{
    const float cell_width = 50;
    const float cell_height = 80;
    const float led_thickness = 7;
    const float gap = 1.5f;
    TimeLedText = new LedText(cell_width,
        cell_height, led_thickness, gap);

    const float scale = 0.95f;
    DateLedText = new LedText(scale * cell_width,
        scale * cell_height, scale * led_thickness,
        scale * gap);
}

This code declares the TimeLedText and DateLedText objects at the form level. The form’s Load event handler defines some constants and creates the objects. It scales the DateLedText object’s parameters by 0.95 (a number I found by trial and error) to make the date text as wide as the time text.

The program’s form contains a timer that fires every 500 milliseconds. When it does, the following code executes.

private void tmrTick_Tick(object sender, EventArgs e)
{
    picClock.Refresh();
}

private void picCanvas_Paint(object sender, PaintEventArgs e)
{
    ShowTime(e.Graphics);
}

// Display the time.
private void ShowTime(Graphics gr)
{
    gr.Clear(Color.Black);
    gr.SmoothingMode = SmoothingMode.AntiAlias;

    const float margin = 5f;
    PointF position = new PointF(margin, margin);
    using (Brush unused_brush = new SolidBrush(Color.FromArgb(0, 40, 0)))
    {
        // Draw the time.
        Brush bg_brush = Brushes.Black;
        Brush used_brush = Brushes.Lime;
        Pen used_pen = Pens.Transparent;
        Pen unused_pen = Pens.Transparent;

        TimeLedText.DrawText(gr, bg_brush,
            used_brush, used_pen,
            unused_brush, unused_pen,
            position, 1.2f,
            DateTime.Now.ToLongTimeString());
    }

    using (Brush unused_brush = new SolidBrush(Color.FromArgb(0, 0, 60)))
    {
        // Draw the time.
        Brush bg_brush = Brushes.Black;
        Brush used_brush = Brushes.Blue;
        Pen used_pen = Pens.Transparent;
        Pen unused_pen = Pens.Transparent;

        position.Y += TimeLedText.CellHeight +
            4 * TimeLedText.LedThickness;

        // Draw the day and date.
        string date_string =
            DateTime.Now.DayOfWeek.ToString();
        date_string = date_string.ToUpper().Substring(0, 3);
        date_string += " " +
            DateTime.Now.Day.ToString() + "/" +
            DateTime.Now.Year.ToString().Substring(0, 2);
        DateLedText.DrawText(gr, bg_brush,
            used_brush, used_pen,
            unused_brush, unused_pen,
            position, 1.2f,
            date_string);
    }
}

The Timer’s Tick event handler refreshes the picClock PictureBox. That control’s Paint event handler calls the ShowTime method to do all of the interesting work.

The ShowTime method is relatively straightforward. It defines some brushes and pens and then passes them to the TimeLedText object’s DrawText method to draw the time. It then repeats those steps to draw the date with different brushes and pens. The only really interesting step is where the code adds space to the Y coordinate of the position where it draws the text so the date is drawn far enough below the time.

Conclusion

This is a relatively simple example. It draws the time and date text at specific positions and sizes to make everything fit nicely. You could modify it to calculate the text sizes at runtime to fit the form, but seemed like more work than it was worth for such a basic example. Feel free to make that change it if you like.

The only other non-obvious detail is that I set the form’s FormBorderStyle property to FixedToolWindow at design time.

Download the example to see additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Draw LED-style letters in C#


[LED-style letters]

Every now and then I’ve had the urge to write a digital clock program, and to do that, I need to be able to draw LED-style letters. (Some things are practically a right of passage for programmers. Writing a program that can reproduce its own code, prime factoring your Social Security Number, writing clocks…)

This program draws LED-style letters so I can make a digital clock in a later post. That post will be a lot easier than this one, but even this one isn’t terribly complicated, although some of it is rather long.

Most of the code consists of methods that draw the LEDs. Other pieces use those methods to the letters. The following sections describe the main pieces of the LedText class that does most of the interesting work.

The LedText Class

The LedText class draws LED-style letters. The following code shows the class’s declaration and constructor.

class LedText
{
    public float CellWidth, CellHeight, LedThickness, Gap;
    public Dictionary<char, bool[]> LetterLeds = null;

    public LedText(float cell_width, float cell_height,
        float led_thickness, float gap)
    {
        CellWidth = cell_width;
        CellHeight = cell_height;
        LedThickness = led_thickness;
        Gap = gap;

        // Define the functions that draw the LEDs.
        DefineLedFuncs();

        // Define the letters.
        DefineLetters();
    }
    ...
}

[LED-style letters]

The class defines several variables that define the text’s geometry. The picture on the right shows how those variables determine the size of the letters’ pieces.

The LetterLeds dictionary holds an array of Boolean variables for each letter. Those values indicate which of the LEDs should be turned on and off for the corresponding letter. The dictionary uses the letter as a key, so it’s easy to find the Boolean values that define the letter.

The class’s constructor saves the geometry values and then calls the DefineLedFuncs and DefineLetters methods. The next section describes DefineLedFuncs. I’ll describe the DefineLetters method a bit later.

DefineLedFuncs

The DefineLedFuncs method shown in the following code builds an array containing references to the methods that draw the LEDs.

// Make an array to hold the LED-drawing functions.
private delegate PointF[] LedFunc(PointF point);
private LedFunc[] LedFuncs = null;

private void DefineLedFuncs()
{
    LedFuncs = new LedFunc[]
    {
        MakeLed0,
        MakeLed1,
        MakeLed2,
        MakeLed3,
        MakeLed4,
        MakeLed5,
        MakeLed6,
        MakeLed7,
        MakeLed8,
        MakeLed9,
        MakeLed10,
        MakeLed11,
        MakeLed12,
        MakeLed13,
    };
}

[LED-style letters]

This code first declares a delegate data type named LedFunc. That type refers to a method that takes as a parameter a PointF and returns an array of PointF. The parameter will be the coordinate of the letter’s upper left corner, and the returned array will hold the points that define that LED’s polygon.

After it declares the LedFunc data type, the code declares an array of those named LedFuncs. The picture on the right shows how the LEDs are numbered. For example, the first entry in the LedFuncs array will hold a reference to the method that builds a polygon for LED number 0, which is a short, wide hexagon.

The DefineLedFuncs method creates the LedFuncs array, filling it with the LED-drawing methods.

Later the drawing methods will use the LedFuncs array to find the methods that they need to draw the LED-style letters.

LED-Drawing Methods

Most of the LED-drawing methods are relatively straightforward. They use fairly simple offsets from the letter’s upper left corner to figure out where each polygon’s vertices must be.

For example, starting at the letter’s upper left corner, the leftmost tip of LED 0 is shifted right and down by half of the LED thickness. The code also adds in the Gap value to provide some separation from the nearby LEDs. (Actually it should probably add the Gap divided by the square root of two or something, but I like the current result so I’m not going to mess with it.)

Several of the LEDs have this basic horizontal hexagon shape, so I wrote a helper method named MakeHLed to define them. I’ll show you that shortly, but first here’s the MakeLed0 method.

public PointF[] MakeLed0(PointF position)
{
    PointF p1 = new PointF(
        position.X + LedThickness / 2f + Gap,
        position.Y + LedThickness / 2f);
    PointF p2 = new PointF(
        position.X + CellWidth - LedThickness / 2f - Gap,
        p1.Y);
    return MakeHLed(p1, p2);
}

This method calculates the coordinates of the hexagon’s leftmost and rightmost points p1 and p2. It then calls the following MakeHLed method to create the LED’s hexagon. (The “H” in the middle of the name stands for “horizontal.”)

public PointF[] MakeHLed(PointF p1, PointF p2)
{
    PointF[] points =
    {
        new PointF(p1.X, p1.Y),
        new PointF(p1.X + LedThickness / 2f, p1.Y + LedThickness / 2f),
        new PointF(p2.X - LedThickness / 2f, p2.Y + LedThickness / 2f),
        new PointF(p2.X, p2.Y),
        new PointF(p2.X - LedThickness / 2f, p2.Y - LedThickness / 2f),
        new PointF(p1.X + LedThickness / 2f, p1.Y - LedThickness / 2f),
    };
    return points;
}

This adds and subtracts halves of the LED thickness to make the hexagon shape starting at the leftmost point p1, moving over to rightmost point p2, and returning to point p1. The code is straightforward, although you do need to be careful while figuring out how to generate the points.

The methods that draw LEDs 0, 6, 7, and 13 all follow this same basic approach.

The methods that draw LEDs 1, 5, 8, and 12 work similarly, although they call the MakeVLed method (the “V” stands for “vertical”) to draw their polygons.

The methods that draw LEDs 3 and 10 take a fairly similar approach, but they use two helper methods, MakeCtLed (“Ct” for “center top”) and MakeCbLed (“Cb” for “center bottom”).

So far these methods are fairly long but straightforward. The methods that draw the diagonal LEDs are more complicated.

Drawing Diagonal LEDs

[LED-style letters]

Unfortunately it’s not as easy to figure out where the vertices should go for the diagonal LEDs. You could use points that are the same distances vertically and horizontally from the inner corners of the existing LEDs, but then the LEDs along each diagonal wouldn’t line up properly.

Instead this program uses points that are offset from the letter cell’s outer corners. The offset is the LED thickness divided by the square root of two, as shown in the picture on the right. That makes the distance between the two points at each corner equal to the LED thickness.

(Note that this does not give the diagonal LEDs quite the same thickness as the others. If the letter cell were a square, the that would be the case. When the cell is taller than it is wide, as it is in this example, then the result is slightly thinner. As was the case with diagonal gaps, I like the result so I’m not going to mess with it.)

[LED-style letters]

The picture then intersects those red diagonal lines with vertical and horizontal lines that lie on the inside edges of the outer polygons drawn so far. The picture on the right shows how the red diagonal lines intersect the blue vertical and horizontal lines to define two of the vertices for LED number 2.

The basic approach used to define the diagonal LEDs is to find intersecting pairs of line segments that define the polygon’s vertices, and then pass them into a method that finds the intersections and uses the resulting points to define the polygon.

The following code shows the method that makes LED number 2.

public PointF[] MakeLed2(PointF position)
{
    float sqrt2 = (float)Math.Sqrt(2.0);
    float dx = LedThickness / sqrt2;
    float dy = dx;

    PointF u_diag_pt1 = new PointF(
        position.X + dx,
        position.Y);
    PointF u_diag_pt2 = new PointF(
        position.X + CellWidth,
        position.Y + CellHeight - dy);

    PointF l_diag_pt1 = new PointF(
        position.X,
        position.Y + dy);
    PointF l_diag_pt2 = new PointF(
        position.X + CellWidth - dx,
        position.Y + CellHeight);

    PointF u_horz_pt1 = new PointF(
        position.X,
        position.Y + LedThickness + Gap);
    PointF u_horz_pt2 = new PointF(
        position.X + CellWidth,
        position.Y + LedThickness + Gap);

    PointF l_horz_pt1 = new PointF(
        position.X,
        position.Y + CellHeight / 2f - LedThickness / 2f - Gap);
    PointF l_horz_pt2 = new PointF(
        position.X + CellWidth,
        position.Y + CellHeight / 2f - LedThickness / 2f - Gap);

    PointF l_vert_pt1 = new PointF(
        position.X + LedThickness + Gap,
        position.Y);
    PointF l_vert_pt2 = new PointF(
        position.X + LedThickness + Gap,
        position.Y + CellHeight);

    PointF r_vert_pt1 = new PointF(
        position.X + CellWidth / 2f - LedThickness / 2f - Gap,
        position.Y);
    PointF r_vert_pt2 = new PointF(
        position.X + CellWidth / 2f - LedThickness / 2f - Gap,
        position.Y + CellHeight);

    PointF[][] segs =
    {
        new PointF[] { l_vert_pt1, l_vert_pt2, u_horz_pt1, u_horz_pt2 },
        new PointF[] { u_horz_pt1, u_horz_pt2, u_diag_pt1, u_diag_pt2 },
        new PointF[] { u_diag_pt1, u_diag_pt2, r_vert_pt1, r_vert_pt2 },
        new PointF[] { r_vert_pt1, r_vert_pt2, l_horz_pt1, l_horz_pt2 },
        new PointF[] { l_horz_pt1, l_horz_pt2, l_diag_pt1, l_diag_pt2 },
        new PointF[] { l_diag_pt1, l_diag_pt2, l_vert_pt1, l_vert_pt2 },
    };
    return MakeIntersectionLed(segs);
}

This method calculates the locations of points that it can use to define the segments. It then creates an array of arrays named segs. Each entry in the segs array is an array containing four points that define two line segments. The point where the segments intersect gives one of the polygon’s vertices.

After it defines the segments, the MakeLed2 method passes the array into the following MakeIntersectionLed method.

public PointF[] MakeIntersectionLed(PointF[][] segs)
{
    List points = new List();

    foreach (PointF[] seg in segs)
    {
        PointF a1 = seg[0];
        PointF a2 = seg[1];
        PointF b1 = seg[2];
        PointF b2 = seg[3];
        bool lines_intersect, segs_intersect;
        PointF intersection, close_pt1, close_pt2;
        FindIntersection(a1, a2, b1, b2,
            out lines_intersect, out segs_intersect,
            out intersection, out close_pt1, out close_pt2);
        points.Add(intersection);
    }

    return points.ToArray();
}

After all that setup, this method is fairly simple. It loops through the segments in the segs array. Each entry in the array contains four points that define two line segments. The method simply calls the FindIntersection method to see where the two segments intersect and adds the resulting point to the points list. When it finishes processing all of the segment pairs, the method converts the list into an array and returns the result.

That’s the end of the code that creates polygons to draw the LEDs. The methods that I haven’t shown here are similar to the others. You can always download the example if you want to see all of their details.

DefineLetters

Recall that the LetterLeds dictionary maps letters as keys to arrays of Boolean values that indicate which LEDs should be drawn. The following code shows the DefineLetters method that fills the LetterLeds dictionary.

// Define the LED methods used to draw letters.
public void DefineLetters()
{
    if (LetterLeds != null) return;
    LetterLeds = new Dictionary();

    LetterLeds.Add((char)0, StringToBool("11111111111111"));

    LetterLeds.Add('0', StringToBool("11000100100011"));
    LetterLeds.Add('1', StringToBool("00001100000010"));
    LetterLeds.Add('2', StringToBool("10000111100001"));
    LetterLeds.Add('3', StringToBool("10000101000011"));
    LetterLeds.Add('4', StringToBool("01000111000010"));
    LetterLeds.Add('5', StringToBool("11000011000011"));
    LetterLeds.Add('6', StringToBool("11000011100011"));
    LetterLeds.Add('7', StringToBool("10000100000010"));
    LetterLeds.Add('8', StringToBool("11000111100011"));
    LetterLeds.Add('9', StringToBool("11000111000011"));
    LetterLeds.Add('A', StringToBool("11000111100010"));
    LetterLeds.Add('B', StringToBool("10010101001011"));
    LetterLeds.Add('C', StringToBool("11000000100001"));
    LetterLeds.Add('D', StringToBool("10010100001011"));
    LetterLeds.Add('E', StringToBool("11000010100001"));
    LetterLeds.Add('F', StringToBool("11000010100000"));
    LetterLeds.Add('G', StringToBool("11000001100011"));
    LetterLeds.Add('H', StringToBool("01000111100010"));
    LetterLeds.Add('I', StringToBool("10010000001001"));
    LetterLeds.Add('J', StringToBool("00000100100011"));
    LetterLeds.Add('K', StringToBool("01001010100100"));
    LetterLeds.Add('L', StringToBool("01000000100001"));
    LetterLeds.Add('M', StringToBool("01101100100010"));
    LetterLeds.Add('N', StringToBool("01100100100110"));
    LetterLeds.Add('O', StringToBool("11000100100011"));
    LetterLeds.Add('P', StringToBool("11000111100000"));
    LetterLeds.Add('Q', StringToBool("11000100100111"));
    LetterLeds.Add('R', StringToBool("11000111100100"));
    LetterLeds.Add('S', StringToBool("11000011000011"));
    LetterLeds.Add('T', StringToBool("10010000001000"));
    LetterLeds.Add('U', StringToBool("01000100100011"));
    LetterLeds.Add('V', StringToBool("01001000110000"));
    LetterLeds.Add('W', StringToBool("01000100110110"));
    LetterLeds.Add('X', StringToBool("00101000010100"));
    LetterLeds.Add('Y', StringToBool("00101000001000"));
    LetterLeds.Add('Z', StringToBool("10001000010001"));
    LetterLeds.Add(' ', StringToBool("00000000000000"));
    //                                -|\|/|--|/|\|-
}

This method makes a new LetterLeds dictionary and then adds items to that list.

Each item that it adds has the form of a letter and the result of a call to the StringToBool helper method described shortly. That method converts a string of zeros and ones into an array of corresponding Boolean values.

The DefineLetters method starts by creating a special entry for the letter (char)0. That entry has every LED turned on so it creates a result similar to several of the pictures shown earlier in this post. A bit later you’ll see how the program uses that entry when you try to draw a letter that is undefined.

After it defines “letter 0,” the code defines entries for the digits 0 through 9, the letters A through Z, and the space character. Feel free to add others if you like.

If you study the entries, you can verify that 0s and 1s identify each letter’s LEDs. For example, the Zs entries activate the top and bottom LEDs, plus the upper-right to lower-left diagonal LEDs.

The comment at the end of the method makes it a little easier to create new entries. The -|\|/|--|/|\|- characters show the direction in which the corresponding LEDs point.

The following code shows the StringToBool helper method.

// Convert a string of the form 10100110...
// into an array of bool.
private bool[] StringToBool(string values)
{
    bool[] result = new bool[values.Length];
    for (int i = 0; i < values.Length; i++)
        result[i] = (values[i] == '1');
    return result;
}

This method creates an array of bool that has the same length as its input string. It then loops through the string’s characters and sets the corresponding array entries to true if the character is 1. When it is finished, the method returns the array.

Drawing Letters

The class does a lot of work to set up the LetterLeds dictionary and the LedFuncs array. Using them is a lot easier.

The following MakeLetterPgons method returns list of polygons needed to draw a letter.

// Make the polygons that represent a letter.
public void MakeLetterPgons(char letter, PointF position,
    out List<PointF[]> used_pgons, out List<PointF[]> unused_pgons)
{
    used_pgons = new List<PointF[]>();
    unused_pgons = new List<PointF[]>();

    bool[] used;
    if (LetterLeds.ContainsKey(letter)) used = LetterLeds[letter];
    else used = LetterLeds[(char)0];

    for (int i = 0; i < used.Length; i++)
    {
        if (used[i])
            used_pgons.Add(LedFuncs[i](position));
        else
            unused_pgons.Add(LedFuncs[i](position));
    }
}

This method uses output parameters to return polygons for the LEDs that should and should not be used to draw the letter.

The method first creates the two lists. It then uses the LetterLeds dictionary’s ContainsKey method to see if the dictionary contains an entry for the letter. If the letter is in the dictionary, then the code fetches its Boolean values and saves them in the used array. If the letter is not in the dictionary, then the code uses the “letter 0” entry’s Boolean values so the letter will be drawn with all LEDs active as a sort of placeholder. (Alternatively you could use the space character to make a blank result.)

The code then loops through the Boolean values in the used array. If an entry is true, the program calls the corresponding LedFuncs method to generate the LED and adds it to the used_pgons list. If the used array entry is false, the program calls the LedFuncs method and saves the result in the unused_pgons list.

The following DrawLetter method uses the MakeLetterPgons method to draw a letter.

// Draw a letter.
public void DrawLetter(Graphics gr, Brush bg_brush,
    Brush used_brush, Pen used_pen,
    Brush unused_brush, Pen unused_pen,
    PointF position, char letter)
{
    // Clear the background.
    gr.FillRectangle(bg_brush,
        position.X, position.Y,
        CellWidth, CellHeight);

    // Draw the polygons.
    List<PointF[]> used_pgons, unused_pgons;
    MakeLetterPgons(letter, position, out used_pgons, out unused_pgons);
    foreach (PointF[] pgon in unused_pgons)
    {
        gr.FillPolygon(unused_brush, pgon);
        gr.DrawPolygon(unused_pen, pgon);
    }
    foreach (PointF[] pgon in used_pgons)
    {
        gr.FillPolygon(used_brush, pgon);
        gr.DrawPolygon(used_pen, pgon);
    }
}

This method fills the letter’s background area with the brush bg_brush. Normally you would probably want the whole drawing area to have this same background color. In that case, this step isn’t really necessary. In fact, if you have some sort of drawing already on the drawing area and you want to draw the letter on top, you might need to not do this. You can pass the brush SolidBrushes.Transparent in as the bg_brush parameter to leave the background unchanged.

Next the code calls the MakeLetterPgons method to get the letter’s polygons. It then loops through the polygons representing inactive LEDs and fills and then outlines them. As with the background, you can pass a transparent brush or pen for the unused_brush and unused_pen parameters if you don’t want to draw one of those items.

The method repeats those steps for the active LEDs and is done. Simple!

Drawing Text

The LedText class has one more method: DrawText. This method uses the following code to draw a string of letters.

// Draw a sequence of letters.
public void DrawText(Graphics gr, Brush bg_brush,
    Brush used_brush, Pen used_pen,
    Brush unused_brush, Pen unused_pen,
    PointF position, float h_spacing, string text)
{
    foreach (char ch in text)
    {
        DrawLetter(gr, bg_brush, used_brush, used_pen,
            unused_brush, unused_pen, position, ch);
        position.X += CellWidth * h_spacing;
    }
}

The code loops through the input string’s letters. The code calls DrawLetter to draw each letter and then adds h_spacing times the letters’ cell width to the X position to draw the next letter shifted to the right.

Normally h_spacing should be something like 1.2 to allow a little space between the letters. You can make that space wider, narrower, or even negative if you like.

The Main Program

The main program includes several test methods. The following method is initially not commented out so the program draws the digits 0 through 9 and the letters A through Z.

private void TestLetters(Graphics gr)
{
    gr.Clear(Color.Black);
    gr.SmoothingMode = SmoothingMode.AntiAlias;

    const float margin = 10;
    const float cell_width = 50;
    const float cell_height = 80;
    const float led_thickness = 7;
    const float gap = 1.5f;

    LedText letter = new LedText(
        cell_width, cell_height, led_thickness, gap);

    Brush bg_brush = Brushes.Black;
    Brush used_brush = Brushes.Lime;
    Pen used_pen = Pens.Transparent;
    Brush unused_brush = new SolidBrush(Color.FromArgb(0, 40, 0));
    Pen unused_pen = Pens.Transparent;

    PointF position = new PointF(margin, margin);
    letter.DrawText(gr, bg_brush, used_brush, used_pen,
            unused_brush, unused_pen, position,
            1.2f, "ABCDEFGHI");

    position.Y += letter.CellHeight * 1.2f;
    letter.DrawText(gr, bg_brush, used_brush, used_pen,
            unused_brush, unused_pen, position,
            1.2f, "JKLMNOPQR");

    position.Y += letter.CellHeight * 1.2f;
    letter.DrawText(gr, bg_brush, used_brush, used_pen,
            unused_brush, unused_pen, position,
            1.2f, "STUVWXYZ0");

    position.Y += letter.CellHeight * 1.2f;
    letter.DrawText(gr, bg_brush, used_brush, used_pen,
            unused_brush, unused_pen, position,
            1.2f, "123456789");
}

This method defines some geometry values and creates a LedText object. It then makes a PointF variable named position to keep track of where the text should be drawn.

The code then calls the DrawText method to draw the string “ABCDEFGHI.” Nxet it adds 1.2 times the letters’ cell height to the position variable’s Y coordinate to move down a line. It repeats those steps, drawing a string of letters and then moving down to a new line, until it has drawn the letters and digits.

Conclusion

This is not a font. You can go online and download LED fonts, but I wanted to build LED-style letters myself and I still don’t know how to create a font from scratch in C#. For now, this program is pretty fun.

Download the example to see all of the details and to experiment with it. Try changing the letter geometry, colors, and brushes. For example, the picture below draws “3” with yellow LEDs outlined in red and “5” with LEDs filled with a green and lime brick pattern.


[LED-style letters]


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in drawing, graphics | Tagged , , , , , , , | 4 Comments