Use the mouse wheel to scale images while cropping them to a desired aspect ratio in C#

[example]

This example adds mouse wheel support to the example Crop scaled images to a desired aspect ratio in C#.

Some social media platforms work best with a 4:3 aspect ratio, so I use that example almost every day to crop images to a particular aspect ratio before posting them. If an image won’t work with that aspect ratio, I sometimes crop them to a 1:1 (square) aspect ratio. You can see some of these images on Facebook, Google business, and The Enchanted Oven.

When I use MS Paint, I often use the mouse wheel to zoom in and out. It’s so easy that I decided to add similar mouse wheel support to the cropping example.

Supporting the mouse wheel isn’t very hard once you know the trick, and there is a trick! The Visual Studio Properties window does not know about the MouseWheel event, so you need to register to catch that event at run time. The following code shows how this example does that.

private void Form1_Load(object sender, EventArgs e)
{
    ...
    // Prepare to use the mouse wheel.
    this.MouseWheel += Form_MouseWheel;
}

After performing setup chores used by the previous example, this program registers the Form_MouseWheel method to catch the form’s MouseWheel event handler.

I’ll show you that event handler shortly. First I want to show you how the program changes the image’s scale.

SetScale

The previous version of this program lets you use the Scale menu’s items to select one of the scale factors 100%, 75%, 66%, 50%, 25%, or 15%. That example made the selection right in the menu items’ shared event handler. The new example needs to allow the Form_MouseWheel event handler to also set the scale factor, so I rearranged the code a bit. Now the program uses the following method to set the scale.

// Set the appropriate scale for this menu item.
private void SetScale(ToolStripMenuItem menu_item)
{
    // Get the scale factor.
    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);
    }
}

This method takes as a parameter the menu item that represents the new scale. It takes the item’s caption, removes any & or % characters that it contains, parses the result, and divides by 100 to get the scale as a floating point number. For example, that transforms the caption &25% into the value 0.25.

The code then calls the ShowScaledImage method to redisplay the image and selection rectangle at the new scale.

The method then displays the new scale factor in the Scale menu’s caption as in “Scale (25%).” (See the picture at the top of this post.)

Finally, the method loops through the Scale menu’s dropdown items, checks the item that came in as the method’s parameter, and unchecks the others.

When you select one of the Scale menu’s items, the following event handler executes.

private void mnuScale_Click(object sender, EventArgs e)
{
    // Get the scale factor.
    SetScale(sender as ToolStripMenuItem);
}

This method simply calls the new SetScale method, passing it the menu item that you selected.

Form_MouseWheel

The following event handler executes when you use the mouse wheel.

// Respond to the mouse wheel.
private void Form_MouseWheel(object sender, MouseEventArgs e)
{
    if (OriginalImage == null) return;

    // Find the current scale.
    int int_scale = (int)(ImageScale * 100);

    // Find the index of the corresponding menu item.
    ToolStripMenuItem[] menu_items =
    {
        mnuScale100,
        mnuScale75,
        mnuScale66,
        mnuScale50,
        mnuScale25,
        mnuScale15,
    };
    List scales = new List()
        { 100, 75, 66, 50, 25, 15 };
    int index = scales.IndexOf(int_scale);

    // If we're zooming out, move to a smaller scale.
    // Else move to a larger scale.
    if (e.Delta < 0) index++;
    else index--;

    // Select the new scale menu item.
    if ((index >= 0) && (index < menu_items.Length))
    {
        SetScale(menu_items[index]);
    }
}

This code first checks to see if an image is loaded and returns if one is not. Next the method multiplies the scale factor, which is something like 0.25, by 100 to get a value like 25.

The method then creates an array holding the Scale menu’s items. It makes a corresponding list of the items’ scale factors.

The code then uses the scales list’s IndexOf method to get the index of the current scale factor. That index is also the index of the currently selected Scale menu item.

If the event handler’s e.Delta parameter is less than zero, then you have moved the mouse wheel down (toward you). In that case, the code increments index to move to a smaller scale factor in the menu_items array. If e.Delta parameter is greater than zero, the code decrements index to move to a larger scale factor.

If the new index has not moved out of the menu_items array, the code calls the SetScale method described earlier, passing it the menu item that represents the new scale factor. The SetScale method then updates the scale factor, displays the image at the new scale, and shows the scale in the Scale menu’s caption.

Conclusion

Using the mouse wheel isn’t hard. The only trick is knowing that you need to install its event handler at run time because the Properties window doesn’t know about it.

See the previous example and download the example to see additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in drawing, graphics, image processing | Tagged , , , , , , , , , | 2 Comments

Allow scrolling while making a hexagonal montage in C#


[hexagonal montage]

The example Make a hexagonal montage of pictures in C# shows how to make a hexagonal montage similar to the one shown above. I sometimes want to make a montage that is much bigger, and that example doesn’t let you do that. There are two obvious approaches. First, you could draw the montage at one scale and then save it at another. Second, you could let the user scroll the montage.

If you take the first approach, then you would need to convert mouse clicks for the scaled result. This is possible, but more work than the second approach, which is practically trivial. This example simply moves the picGrid PictureBox control that displays the hexagonal montage into a Panel control. That control’s AutoScroll property is set to true, so it automatically displays scroll bars if picGrid is too large to fit.

That’s all there is to it. The picGrid control reports mouse positions in its normal coordinate system even if you have use the panel to move the picture to a new location. The program then uses the mouse clicks to determine which hexagonal area you have clicked.

Download the example to experiment with it. Look at the code and look at the previous post to see additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Use XAML data binding to display an animated rectangle’s location

[XAML data binding]

This example uses storyboard animation to move a rectangle and uses XAML data binding to display the rectangle’s location as it moves. It’s actually fairly simple, at least a far as WPF goes, once you figure out how to do it.


Here’s how the program’s XAML code begins.

<Window x:Class="howto_xaml_rectangle_storyboard.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="howto_xaml_rectangle_storyboard"
    Height="300" Width="300">
    <Canvas Background="Pink">
        <Rectangle Name="myRectangle" Width="40" Height="30"
            Fill="LightBlue" Stroke="Blue" StrokeThickness="2"
            Margin="20,20">
            <Rectangle.Triggers>
                <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                    <BeginStoryboard>
                        <Storyboard RepeatBehavior="Forever">
                            <ThicknessAnimation To="220,20"
                                BeginTime="0:0:0" Duration="0:0:1"
                                Storyboard.TargetProperty="(Margin)"
                                AutoReverse="False" />
                            <ThicknessAnimation To="220,220"
                                BeginTime="0:0:1" Duration="0:0:1"
                                Storyboard.TargetProperty="(Margin)"
                                AutoReverse="False" />
                            <ThicknessAnimation To="20,220"
                                BeginTime="0:0:2" Duration="0:0:1"
                                Storyboard.TargetProperty="(Margin)"
                                AutoReverse="False" />
                            <ThicknessAnimation To="20,20"
                                BeginTime="0:0:3" Duration="0:0:1"
                                Storyboard.TargetProperty="(Margin)"
                                AutoReverse="False" />
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Rectangle.Triggers>
        </Rectangle>

This code defines the program’s Window, places a Canvas inside it, and then creates a Rectangle inside that. The code sets some Rectangle properties and then defines a Triggers section.

The Rectangle has a single trigger that executes when the control is loaded. The trigger executes a BeginStoryboard, which contains a Storyboard that repeats indefinitely.

The Storyboard contains three ThicknessAnimation objects that adjust the Rectangle control’s Margin property. The animation objects have BeginTime and Duration properties set so each begins when the previous one ends. The result of the animations is to move the rectangle to the right across the the window, down the right edge, to the left across the bottom, and up along the left edge until the rectangle reaches its starting point.

The following code shows the rest of the program’s XAML.

        <Label Margin="10,0"
            HorizontalAlignment="Left" VerticalAlignment="Top"
            ContentStringFormat="0"
            HorizontalContentAlignment="Center"
            Content="{Binding ElementName=myRectangle, Path=Margin.Left}"/>
        <Label Margin="30,0"
            HorizontalAlignment="Left" VerticalAlignment="Top"
            Content=","/>
        <Label Margin="35,0"
            HorizontalAlignment="Left" VerticalAlignment="Top"
            ContentStringFormat="0"
            HorizontalContentAlignment="Center"
            Content="{Binding ElementName=myRectangle, Path=Margin.Top}"/>
    </Canvas>
</Window>

This code defines three labels, two to display the rectangle’s X and Y coordinates, and one to display a comma in between. The first label sets its Content property to the XAML data binding {Binding ElementName=myRectangle, Path=Margin.Left}. That makes the label display the myRectangle object’s Margin.Top property. When the animations modify the rectangle’s top margin value, this label displays the new value.

The label’s ContentStringFormat property makes the control display the value as an integer. If you don’t set that property, the label displays a ridiculously inappropriate number of digits after the decimal point.

The third label uses XAML data binding similarly to display the rectangle’s Margin.Top value.

Conclusion

That’s all there is to this example. Everything is done in XAML code including the XAML data binding; there’s no code behind. Download the example to experiment with the program.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in animation, wpf, XAML | Tagged , , , , , , , , , | Leave a comment

Print product signs in C#


product signs

This is a program I recently wrote to print product signs for my bakery The Enchanted Oven. You can modify the code to use your product names and prices.

In the program’s main form (upper left in the picture), select the items for which you want to generate product signs. Next pick a divider type: Grid (shown in the picture), Spaced Grid (a grid with a blank margin between cells), or Cut Marks (little plus signs at the grid cell corners).

Set the minimum and maximum X and Y coordinates that you want the signs to cover on the printed page in inches. Note that the program prints in landscape orientation. You can modify the code to print in portrait orientation if you like. These values indicate the left and right page margins, but they also let you position the text in the product signs vertically. To make a good sign, they should not be centered vertically.

After you’ve made your selections, click the Print button to display the Print Preview Dialog (lower right in the picture above). Use the dialog to view the printout’s pages. If you like the result, click the Print button in the dialog’s upper left corner to send the printout to the printer.

product signs

After the printout is finished, I cut the three columns apart. I then fold the strips along the horizontal lines and tape the result closed to form a triangular prism that I can use for product signs as in the picture on the right.

The program demonstrates some useful techniques including:

  • Storing product data
  • Checking and unchecking items in a CheckedListBox
  • Handling the PrintDocument class’s PrintPage event
  • Handling the PrintDocument class’s BeginPrint event
  • Printing data across multiple pages

The following sections describe those techniques and explain other interesting pieces of code.

Storing Product Data

This example uses a list of Product objects to store the product data. The following code shows the Product class.

public class Product
{
    public string Name;
    public decimal Price;
    public Product(string name, decimal price)
    {
        Name = name;
        Price = price;
    }

    public override string ToString()
    {
        return Name + " (" + Price.ToString("c") + ")";
    }
}

This class basically just stores a product’s name and price. It has one initializing constructor to make creating objects easier.

The class also overrides its ToString method to display the product’s name followed by its price in parentheses. More on that in a bit.

Initializing Products

The following code shows how the program initializes its product data.

private List<Product> Products = new List<Product>();

// Load the data.
private void Form1_Load(object sender, EventArgs e)
{
    // Cake slices.
    string[] cakes =
    {
        "Black Forest Cake",
        "Strawberry Chocolate Mousse Cake",
        ...
        "Matcha Tiramisu",
    };
    foreach (string cake in cakes)
    {
        Products.Add(new Product(cake, 5.49m));
    }

    // Croissants.
    Products.Add(new Product("Plain Croissant", 2.49m));
    Products.Add(new Product("Mini Plain Croissant", 1.49m));
    ...
    Products.Add(new Product("Mini Pecan Croissant", 2.49m));

    string[] fruits =
    {
        "Cherry",
        "Apple",
        ...
        "Raspberry Cream Cheese",
    };
    foreach (string fruit in fruits)
    {
        Products.Add(new Product(fruit + " Croissant", 3.99m));
    }
    Products.Add(new Product("Mini Fruit Croissant", 2.49m));

    // Rolls.
    ...
    Products.Add(new Product("Mini Puff Pastry", 1.99m));

    clbProducts.DataSource = Products;
}

The code starts by defining a list of Product objects. The form’s Load event handler adds objects to the list.

The program has a couple of categories that share the same prices, so the code make arrays holding their names and then loops through the arrays to generate the corresponding Product objects

This code finishes by setting the clbProducts CheckedListBox control’s DataSource property equal to the list. List boxes and combo boxes use the ToString methods of the objects that they contain to determine what to display. The Product class overrides its ToString method to display the product’s name and price, so that’s what the clbProducts control displays.

Checking and Unchecking Items

At design time I set the checked list box’s CheckOnClick property to true, so if you click a row the control checks its box. (If you don’t set that property to true, then you need to select a row and then click it again to check its box.) That lets to check and uncheck list items manually. The All and None buttons let you check or uncheck all of the items at once. The following code shows how those buttons work.

private void btnAll_Click(object sender, EventArgs e)
{
    CheckUncheckAll(true);
}

private void btnNone_Click(object sender, EventArgs e)
{
    CheckUncheckAll(false);
}

The buttons’ event handlers simply call the following CheckUncheckAll method.

private void CheckUncheckAll(bool check)
{
    for (int i = 0; i < clbProducts.Items.Count; i++)
    {
        clbProducts.SetItemChecked(i, check);
    }
}

This method loops through all of the items in the list and calls the control’s SetItemChecked for each to check or uncheck them. (You might think the control would provide a method to check or uncheck all of the items, but no. You could turn it into an extension method, or maybe two.)

When you check or uncheck an item, the following code enables the Print button if at least one item is checked.

private void clbProducts_ItemCheck(object sender, ItemCheckEventArgs e)
{
    if (clbProducts.CheckedItems.Count == 1 &&
        e.NewValue == CheckState.Unchecked)
    {
        // The collection is about to be empty.
        btnPrint.Enabled = false;
    }
    else
    {
        // The collection is about to be non-empty.
        btnPrint.Enabled = true;
    }
}

This is another place where you might think the control could be improved. Instead of providing an ItemChecked event to tell you when an item has been checked or unchecked, the control only provides the ItemCheck event that tells you what is going to happen. You need to figure out what the consequences of that will be.

The code shown above checks the number of items currently checked. If only one item is checked and the clicked item is about to be unchecked, then there will soon be no checked items. In that case the code disabled the Print button.

If there will soon be at least one checked item, the code enables the Print button.

Printing

When you click Print, the following code executes.

private void btnPrint_Click(object sender, EventArgs e)
{
    ppdSigns.Document = pdocSigns;
    ppdSigns.ClientSize = new Size(700, 600);
    ppdSigns.ShowDialog();
}

This code sets the ppdSigns PrintPreviewDialog component’s Document property equal to the pdonSigns PrintDocument object. The dialog uses the document to generate the printout and then displays it to the user.

The code sets the dialog’s size to make it larger than normal and then displays it.

When the dialog needs to generate a printout, it raises the print document’s events to make it produce the printout. The first event uses by this example is BeginPrint. The following code shows the event handler.

private int NextProductNum = 0;

private void pdocSigns_BeginPrint(object sender,
    System.Drawing.Printing.PrintEventArgs e)
{
    float xmin = float.Parse(txtXmin.Text);
    float xmax = float.Parse(txtXmax.Text);
    float ymin = float.Parse(txtYmin.Text);
    float ymax = float.Parse(txtYmax.Text);
    Xmin = (int)(xmin * 100);
    Xmax = (int)(xmax * 100);
    Ymin = (int)(ymin * 100);
    Ymax = (int)(ymax * 100);
    CellWid = (int)(100 * (xmax - xmin) / 3f);
    CellHgt = (int)(100 * (ymax - ymin) / 2f);

    pdocSigns.DefaultPageSettings.Margins =
        new System.Drawing.Printing.Margins(50, 50, 50, 50);
    pdocSigns.DefaultPageSettings.Landscape = true;

    NextProductNum = 0;
}

The program uses the variable NextProductNum to keep track of the next product that it should print. You could set this value to 0 in the Print button’s event handler, but the dialog may actually need to generate the printout several times. It creates the printout when it first appears so it can show it to you. If you click the dialog’s Print button in its upper left corner, the dialog uses the PrintDocument events again to regenerate the printout to send it to the printer. If you click the button repeatedly, the dialog generates the printout again and again.

If you reset NextProductNum the the program’s btnPrint_Click event handler, then it is only reset once and not each time the print dialog needs to generate the printout.

The BeginPrint event handler solves this problem. It first parses the information that you entered on the program’s main form. Notice that the code multiplies the minimum and maximum X and Y coordinates by 100. The printer measures in hundredths of inches, so that converts the inches that you enter into the printer’s units.

Next the code sets the printout’s page margins (again in hundredths of inches) and makes the page print in landscape mode. (I actually should make the right margin slightly wider because my printer doesn’t print all the way to half an inch from the edge of the paper so the grid’s right edges are cropped off.) It finishes by resetting NextProductNum to 0 so the printout starts with the first product.

When it needs to generate a printed page, the print dialog raises the print document’s PrintPage event handler. This is where the drawing occurs. The following code shows the program’s PrintPage event handler.

private void pdocSigns_PrintPage(object sender,
    System.Drawing.Printing.PrintPageEventArgs e)
{
    DrawPage(e.Graphics);
    e.HasMorePages = (NextProductNum < clbProducts.CheckedItems.Count);
}

This code calls the DrawPage method, passing it the Graphics object in which to draw, to do all of the most interesting work. It then sets e.HasMorePages to tell the print dialog whether there are more product signs to print.

The following section describes the code that actually draws the product signs on the printout.

Drawing

The following DrawPage method draws a single page of product signs on the printout.

private void DrawPage(Graphics gr)
{
    // Draw the products.
    DrawProduct(gr, 0);
    DrawProduct(gr, 1);
    DrawProduct(gr, 2);

    if (radGrid.Checked)
    {
        // Draw the grid.
        int x = Xmin;
        int y = Ymin;
        for (int i = 0; i < 3; i++)
        {
            gr.DrawLine(Pens.Black, x, y, Xmax, y);
            y += CellHgt;
        }
        for (int i = 0; i < 4; i++)
        {
            gr.DrawLine(Pens.Black, x, Ymin, x, Ymax);
            x += CellWid;
        }
    }
    else if (radSpacedGrid.Checked)
    {
        // Draw the spaced grid.
        for (int r = 0; r < 2; r++)
        {
            int y = Ymin + r * CellHgt;
            for (int c = 0; c < 3; c++)
            {
                int x = Xmin + c * CellWid;
                Rectangle rect = new Rectangle(
                    x + CELL_MARGIN, y + CELL_MARGIN,
                    CellWid - 2 * CELL_MARGIN,
                    CellHgt - 2 * CELL_MARGIN);
                gr.DrawRectangle(Pens.Black, rect);
            }
        }
    }
    else
    {
        // Draw cut marks.
        const int tic = 5;
        for (int i = 0; i < 3; i++)
        {
            float y = Ymin + i * CellHgt;
            float[] xs =
                { Xmin, Xmin + CellWid, Xmin + 2 * CellWid, Xmax };
            foreach (float x in xs)
            {
                gr.DrawLine(Pens.Black, x - tic, y, x + tic, y);
            }
        }
        for (int i = 0; i < 4; i++)
        {
            float x = Xmin + i * CellWid;
            float[] ys = { Ymin, Ymin + CellHgt, Ymax };
            foreach (float y in ys)
            {
                gr.DrawLine(Pens.Black, x, y - tic, x, y + tic);
            }
        }
    }
}

This method first calls the DrawProduct method shown shortly to draw three product signs. It passes that method the Graphics object on which to draw and the column number where it should draw each sign.

The method then draws an appropriate grid. That code isn’t short, but it is straightforward so I won’t describe it.

The following code shows the DrawProduct method that draws a single product sign.

private const int CELL_MARGIN = 5;
private const float NAME_SIZE = 30;
private const float PRICE_SIZE = 18;

// Draw this product.
private void DrawProduct(Graphics gr, int col_num)
{
    if (NextProductNum >= clbProducts.CheckedItems.Count) return;

    Product product =
        clbProducts.CheckedItems[NextProductNum] as Product;

    // *****************
    // Draw upside down.
    // *****************
    // Draw the price upside down.
    RectangleF rect;
    rect = new RectangleF(
        Xmin + col_num * CellWid,
        Ymin,
        CellWid, CellHgt * 0.25f);
    rect.Inflate(-CELL_MARGIN, -CELL_MARGIN);
    DrawText(gr, PRICE_SIZE, rect, 180,
        product.Price.ToString("c"));

    // Draw the name upside down.
    rect = new RectangleF(
        Xmin + col_num * CellWid,
        Ymin + CellHgt * 0.5f,
        CellWid, CellHgt * 0.5f);
    rect.Inflate(-CELL_MARGIN, -CELL_MARGIN);
    DrawText(gr, NAME_SIZE, rect, 180,
        product.Name);

    // Draw the divider upside down.
    rect = new RectangleF(
        Xmin + col_num * CellWid,
        Ymin + CellHgt * 0.25f,
        CellWid, CellHgt * 0.25f);
    DrawDivider(gr, rect, 180, Properties.Resources.divider);

    // *******************
    // Draw right-side up.
    // *******************
    // Draw the name right-side up.
    rect = new RectangleF(
        Xmin + col_num * CellWid,
        Ymin + CellHgt,
        CellWid, CellHgt * 0.5f);
    rect.Inflate(-CELL_MARGIN, -CELL_MARGIN);
    DrawText(gr, NAME_SIZE, rect, 0,
        product.Name);

    // Draw the price right-side up.
    rect = new RectangleF(
        Xmin + col_num * CellWid,
        Ymin + CellHgt + CellHgt * 0.75f,
        CellWid, CellHgt * 0.25f);
    rect.Inflate(-CELL_MARGIN, -CELL_MARGIN);
    DrawText(gr, PRICE_SIZE, rect, 0,
        product.Price.ToString("c"));

    // Draw the divider right-side up.
    rect = new RectangleF(
        Xmin + col_num * CellWid,
        Ymin + CellHgt + CellHgt * 0.5f,
        CellWid, CellHgt / 4);
    DrawDivider(gr, rect, 0, Properties.Resources.divider);

    // Prepare to draw the next product.
    NextProductNum++;
}

This is the method that draws the product signs. If first checks NextProductNum to see if we have finished printing all of the signs. If we have run out of product signs, the method just returns. The DrawPage method still draws the grid around the sign’s position, so the page may contain blank product signs. We draw on those with a marker if we need extra signs in the bakery.

The code then gets the next Product object from the Products list and it starts drawing the appropriate sign.

The code follows roughly the same approach to draw the product name, product price, and a divider both right-side up and upside down. Each of those pieces uses the same approach. It calculates the rectangle that should contain the piece, shrinks the rectangle slightly if it will contain text (so the text doesn’t get too close to the grid’s edges), and then calls either DrawText or DrawDivider to draw the piece.

After it finishes drawing all of the sign’s pieces, the method increments NextProductNum so it draw the next product sign the next time it is called.

DrawText

The following code shows the DrawText method.

private void DrawText(Graphics gr, float font_size,
    RectangleF rect, float angle, string text)
{
    using (StringFormat sf = new StringFormat())
    {
        sf.Alignment = StringAlignment.Center;
        sf.LineAlignment = StringAlignment.Center;

        GraphicsState state = gr.Save();
        gr.ResetTransform();

        RectangleF drawing_rect = new RectangleF(
            -rect.Width / 2f, -rect.Height / 2f,
            rect.Width, rect.Height);
        gr.RotateTransform(angle);
        gr.TranslateTransform(
            rect.X + rect.Width / 2f,
            rect.Y + rect.Height / 2f,
            MatrixOrder.Append);

        // Try smaller fonts until the text fits.
        int wid = (int)rect.Width;
        for (float f_size = font_size; f_size > 6; f_size -= 0.5f)
        {
            using (Font font = new Font("Times New Roman", f_size))
            {
                // See if the text will fit.
                SizeF size = gr.MeasureString(text, font, wid, sf);
                if (size.Height <= rect.Height)
                {
                    gr.DrawString(text, font, Brushes.Black,
                        drawing_rect, sf);
                    break;
                }
            }
        }

        gr.Restore(state);
    }
}

The DrawText method draws text inside a rectangle with a given angle of rotation. For this example, the angle is either 0 (right-side up) or 180 (upside down).

The method first creates a StringFormat object and prepares it to draw the text centered in the rectangle.

The code then saves the Graphics object’s state so it can restore it later and resets the object’s transformations to remove any previous ones.

Next, the code creates a rectangle the same size as the target rectangle but centered at the origin. It then applies a rotation to the Graphics object so the text is drawn right-side up or upside down as desired. It then appends a translation transformation to move the result to the original rectangle where the text should appear. Now if the program draws text inside the rectangle at the origin, the text will be rotated and then translated into the desired position.

Before it draws the text, however, the code makes one adjustment. Some of the product signs have long names so their text doesn’t fit within the target rectangle. To fix that, the method makes variable font_size loop from the desired font size down to 6, decreasing by 0.5 each time through the loop. Inside the loop, the code creates a font with the current size and uses the Graphics object’s MeasureString method to see how large the text would be in the test font.

If the text will fit within the target rectangle, the program draws the text and breaks out of the loop. If the text doesn’t fit, the loop continues to try the next smaller font size.

The method finishes by restoring the saved graphics state.

DrawDivider

The following DrawDivider method draws a divider much as the DrawText method draws text.

private void DrawDivider(Graphics gr,
    RectangleF rect, float angle, Image image)
{
    GraphicsState state = gr.Save();
    gr.ResetTransform();

    float wid = rect.Width;
    float hgt = rect.Height;
    if (wid / hgt > image.Width / image.Height)
    {
        // The rectangle is too short and wide. Make it narrower.
        wid = hgt * (image.Width / image.Height);
    }
    else
    {
        // The rectangle is too tall and thin. Make it shorter.
        hgt = wid / (image.Width / image.Height);
    }
    RectangleF dest_rect = new RectangleF(
        -wid / 2f, -hgt / 2f, wid, hgt);

    gr.RotateTransform(angle);
    gr.TranslateTransform(
        rect.X + rect.Width / 2f,
        rect.Y + rect.Height / 2f,
        MatrixOrder.Append);
    gr.DrawImage(image, dest_rect);

    gr.Restore(state);
}

Like the DrawText method, DrawDivider first saves the Graphics object’s state and resets its transformations.

The method then resizes the divider so it is as large as possible but still fits within the target rectangle. To do that, it compares the target rectangle’s aspect ratio (width-to-height ratio) of that of the divider image. (If you look back at the DrawProduct method, you’ll see that the image is stored in the project’s property Properties.Resources.divider.)

If the rectangle is too short and wide, the code makes it narrower so it has its original height but has a width that gives it the correct aspect ratio. Similarly, if the rectangle is too tall and thin, the code makes it shorter so it has the right aspect ratio.

Next the method makes a rectangle of the correct dimensions centered at the origin. It adds transformations to the Graphics object to rotate the divider and translate it so it is placed at the center of the original destination rectangle. Finally, it draws the divider image and restores the Graphics object’s saved state.

Conclusion

This example prints product signs and that may be useful to you. More importantly, it demonstrates some useful printing techniques including:

  • Associating a PringDocument with a PrintDialog
  • Keeping track of the next item to print and using the BeginPrint event handler to restart printing from the beginning
  • Resizing the print dialog
  • Printing in landscape mode
  • Drawing rotated text at a particular location
  • Resizing text to fit a target rectangle
  • Drawing rotated images as large as possible without distortion within a target rectangle

You may even be able to use the DrawText and DrawDivider methods with minimal modification.

Download the example to see additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Display flash cards in C#

[flash cards]

This example displays flash cards created by two pervious examples. The post Split images into halves in C# shows how to separate the prompt and result flash card images. The post Label images at the bottom in C# shows how to add a label to the bottom of the result images to make them easier to understand.

This example displays the flash cards created by the two previous posts.

A and B Files

Physical flash cards typically have a challenge on one side and a solution on the other. The user would look at the challenge picture, mentally decide what it means, and then flip the card over to see if the guess is correct.

This program uses pairs of image files to represent the sides of the flash cards. The two files should have the same names but the challenge file’s name should end in _a and the solution file’s name should end in _b, both before the file extension.

For example, the following pictures show the files a_a.png and a_b.png. (Actually the file names on the web site are slightly different.)

[flash cards] [flash cards]

Using the Program

To use the program, invoke the File menu’s Open command to select a file in the directory that contains the flash cards. (I would use the folder selection dialog to let you select the folder instead of a file, but that dialog is terrible.)

When you select the file, the program reads the images in that file’s directory and saves the pairs. (If a file is missing its pair, for example an _a file without a corresponding _b file, then the file is ignored.)

The program randomizes the pairs and displays one of the challenge images in an _a file.

At this point, you should mentally decide what the image means and then click the image to reveal the solution image. The program then enables the happy and sad face buttons. Click one of them to record whether you were correct. The program updates the counts of correct and incorrect answers and then displays the next challenge image.

The program continues displaying images until you have seen all of the images in the directory. It then displays a message indicating your overall score.

If you like, you can use the File menu’s Open command to open a file, possibly in a new directory, and start over again.

Program States

The example’s code isn’t terribly complicated. The main challenge is keeping track of the program’s state because it has several and you move among them in different ways. The following flow chart shows how the program moves through its states. The boxes represent states. Text between the boxes represents user actions.


[flash cards]

Code

The following sections describe the program’s code.

The Card Class

The program stores information about the flash cards in the following Card class.

public class Card
{
    public Bitmap ASide, BSide;

    public Card(FileInfo a_info, FileInfo b_info)
    {
        ASide = LoadBitmapUnlocked(a_info.FullName);
        BSide = LoadBitmapUnlocked(b_info.FullName);
    }

    // Load a bitmap without locking it.
    private Bitmap LoadBitmapUnlocked(string file_name)
    {
        using (Bitmap bm = new Bitmap(file_name))
        {
            return new Bitmap(bm);
        }
    }
}

This class’s job is to hold the pairs of images that define the flash cards. It stores the images in its ASide and BSide properties.

The class’s only constructor takes as parameters two FileInfo objects that represent the flash card’s A and B sides. The constructor calls the LoadBitmapUnlocked method to load them into bitmaps and saves the bitmaps in the ASide and BSide properties. For more information on the LoadBitmapUnlocked method, see the post Load images without locking their files in C#.

Loading Files

When you select the File menu’s Open command, the program uses the following code to load flash cards.

private void mnuFileOpen_Click(object sender, EventArgs e)
{
    if (ofdDirectory.ShowDialog() == DialogResult.OK)
    {
        // Load the flash cards.
        LoadFiles(ofdDirectory.FileName);

        // Randomize the cards and start a session.
        Cards.Randomize();
        NumCorrect = 0;
        NumWrong = 0;
        RoundNumber = -1;

        ShowNextRound();
    }
}

This code displays an OpenFileDialog to let you select a file in the flash cards’ directory. If you select a file and click Open, the program calls the LoadFiles method described shortly to load the flash cards into the Cards list.

It then calls the list’s Randomize extension method to randomize the cards. For information on that extension method, see the post Make extension methods that randomize arrays and lists in C#.

The code resets the NumCorrect, NumWrong, and RoundNumber values, and calls the ShowNextRound method to start the new round of testing.

The following code shows the LoadFiles method, which performs some interesting LINQ queries.

private List<Card> Cards = null;

private void LoadFiles(string filename)
{
    DirectoryInfo dir_info = (new FileInfo(filename)).Directory;
    var a_query =
        from FileInfo file_info in dir_info.GetFiles("*_a.*")
        orderby file_info.Name
        select file_info;
    List<FileInfo> a_s = a_query.ToList();
    var b_query =
        from FileInfo file_info in dir_info.GetFiles("*_b.*")
        orderby file_info.Name
        select file_info;
    List<FileInfo> b_s = b_query.ToList();

    var card_query =
        from FileInfo a_info in a_s
        join FileInfo b_info in b_s
          on a_info.Name.Replace("_a.", ".") equals
             b_info.Name.Replace("_b.", ".")
        select new Card(a_info, b_info);
    Cards = card_query.ToList();

    btnCorrect.Text = "";
    btnWrong.Text = "";
    btnCorrect.Enabled = true;
    btnWrong.Enabled = false;
}

The program defines the Cards list at the form level. This list holds the list of Card objects that represent the loaded flash cards.

The LoadFiles method takes as a parameter the name of the file that you selected to define the flash card directory. It creates a FileInfo object for that file, and then gets the DirectoryInfo object that represents its containing directory.

The code then defines two LINQ queries. The first selects FileInfo objects for the files in the directory that have names matching the pattern *_a.*. The second query is similar except it selects files with names matching *_b.*. The code executes both queries into lists.

Next the code creates a third LINQ query to select pairs of items from the two lists where replacing _a. in the first list with . gives the same name as replacing _b. in the second list with .. For example, the new query would pair files with names ee_a.png and ee_b.png. (I think you could fool this with some very strange file names like A_a._b.png and _b._a.png, but why would you want to do that? Seriously. Go watch a movie or something instead.)

This is an “inner join” so if a name in the _a list is not matched with a name in the _b list, or vice versa, then no pair is created. That allows you to store non-flash card files in the same directory with the flash cards and the program will ignore those files as long as they do not come in matched pairs of the form *_a.* and *_b.*.

The program executes the query and saves the resulting list of FileInfo objects in the Cards list.

Clicking the Challenge Picture

After the program displays a challenge image, you mentally decide what it means and click the image. When you do, the following code executes.

private void picASide_Click(object sender, EventArgs e)
{
    btnCorrect.Enabled = true;
    btnWrong.Enabled = true;
    picBSide.Image = Cards[RoundNumber].BSide;
}

This code simply enables the happy and sad buttons, and displays the result picture.

Clicking Happy or Sad

After the program displays a result picture, you click the happy or sad buttons to indicate whether you were correct. That executes one of the following event handlers.

private int NumCorrect, NumWrong, RoundNumber;

// Record a correct answer.
private void btnCorrect_Click(object sender, EventArgs e)
{
    NumCorrect++;
    ShowNextRound();
}

// Record an incorrect answer.
private void btnWrong_Click(object sender, EventArgs e)
{
    NumWrong++;
    ShowNextRound();
}

When you click the happy button, the btnCorrect_Click event handler executes. It increments the NumCorrect variable and calls ShowNextRound to display the next challenge picture.

The sad button works similarly except it increments the NumWrong variable.

Showing the Next Round

The ShowNextRound method shown in the following code displays the next round.

private void ShowNextRound()
{
    picBSide.Image = null;
    btnCorrect.Text = NumCorrect.ToString();
    btnWrong.Text = NumWrong.ToString();
    btnCorrect.Enabled = false;
    btnWrong.Enabled = false;

    RoundNumber++;
    if (RoundNumber < Cards.Count)
    {
        picASide.Image = Cards[RoundNumber].ASide;
        picASide.Enabled = true;
    }
    else
    {
        picASide.Image = null;
        picASide.Enabled = false;
        int total = NumCorrect + NumWrong;
        double percent = NumCorrect / (double)total;
        MessageBox.Show("You answered " +
            NumCorrect.ToString() + " out of " +
            total.ToString() +
            " correctly for a score of " +
            percent.ToString("P0"));
    }
}

This method displays the next challenge image. To get the program ready to handle your next selection, it clears the solution image, displays the current number of correct and incorrect answers in the happy and sad buttons, and disables those buttons.

The code then increments the round number. If RoundNumber is now less than the number of objects in the Cards list, then the program has not displayed all of the flash cards. In that case, the code displays the next challenge image in the picASide control and enables that control so you can click it when you are ready.

If the program has displayed all of the flash cards, the code clears the challenge image and disables its control. The code calculates the percentage of correct selections and displays the score. At this point you must select a new file in the flash cards directory to start over.

Conclusion

This program isn’t terribly complicated, it just seems that way because the user changes the program’s state in several different ways. If you keep track of each transition separately, the code isn’t too bad.

As always, download the example to experiment with it and to see additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Label images at the bottom in C#


[label images]

The second step in building a Hiragana flash card program is to label images so you can understand what they mean. My last post in the series, Split images into halves in C#, showed how to split an image into two pieces and remove whitespace from around the pieces. This example places a label at the bottom of the image.

Using the Program

To use the program, enter the From Directory where the unlabeled images are and click List to make the program list the image files on the left.

Enter a destination directory where the labeled images should be placed in the To Directory text box.

When you click an image file’s name in the list, the program displays the image at the bottom.

It’s actually not hard to label images. The program is complicated quite a bit because it allows several (admittedly not completely necessary) options. To change the label’s font, click the ellipsis to the right of the alphabet sample text to display the following font selection dialog.


[label images]

If you select a font and click OK, the program updates its sample font. Notice that the font selection dialog includes a color dropdown that lets you pick from a palette of standard colors. If you select one of the colors, the program uses it for its sample text.

If you click the BG Color sample, the program displays a color selection dialog where you can pick the label area’s background color.

Enter the desired message height in the Msg Hgt text box. This tells the program how tall to make the area where the label is drawn. You may need to adjust this height depending on the size of the font that you selected with the font selection dialog.

Use the Alignment radio buttons to determine how the label is aligned. You can align it in the label area’s top left, top middle, bottom right, etc.

Normally you will want to set the label area height, font, font color, background color, and alignment once and then process several pictures.

Click on the file list to load an image, enter its caption in the Message text box, and click Draw to display the result. If you like it, click Save to save the result. The program gives the saved file the same name as the original file but in the To Directory.(It also beeps so you know that it did something.)

The following sections describe the program’s most interesting pieces of code.

ListFiles

The following ListFiles method builds the list of image files in the program’s list box.

private void ListFiles()
{
    lstFiles.Items.Clear();

    DirectoryInfo dir_info = new DirectoryInfo(txtFromDir.Text);
    foreach (FileInfo file_info in dir_info.GetFiles())
    {
        string extension = file_info.Extension.ToLower();
        if ((extension == ".png") ||
            (extension == ".jpg") ||
            (extension == ".gif") ||
            (extension == ".tiff") ||
            (extension == ".jpeg"))
        {
            lstFiles.Items.Add(file_info);
        }
    }
}

The method first clears the lstFiles ListBox control. It then creates a DirectoryInfo object for the directory named in the txtFromDir TextBox.

The code then calls the DirectoryInfo object’s GetFiles method to list the files in the directory and loops through those files.

If a file’s extension is png, jpg, gif, tiff, or jpeg, the program adds its FileInfo object to the list box. The FileInfo class’s ToString method returns the file’s name without its directory path. The ListBox control uses the ToString methods of the objects that it contains to decide what to display, so the ListBox displays the files’ names without the directory path.

Clicking on a File

When you click on an entry in the file list, the following code executes.

private void lstFiles_SelectedIndexChanged(object sender, EventArgs e)
{
    FileInfo file_info = lstFiles.SelectedItem as FileInfo;
    picImage.Image = LoadBitmapUnlocked(file_info.FullName);
}

The ListBox control stores its items as the generic object type. The event handler converts the selected object into a FileInfo object. It uses that object’s FullName property to get the file’s name including path, and calls the LoadBitmapUnlocked method to read the file. It displays the result in the picImage picture box.

For information on the LoadBitmapUnlocked method, see the post Load images without locking their files in C#.

Drawing the Label

When you click the Draw button, the program uses the following code to create the labeled image.

private void btnDraw_Click(object sender, EventArgs e)
{
    ApplyLabel();
}

// Add the label to the image.
private void ApplyLabel()
{
    FileInfo file_info = lstFiles.SelectedItem as FileInfo;
    if (file_info == null) return;

    using (Bitmap bm = LoadBitmapUnlocked(file_info.FullName))
    {
        picImage.Image = LabelBitmap(bm);
    }
}

The Draw button’s Click event handler simply calls the ApplyLabel method.

The ApplyLabel method converts the list box’s selected item into a FileInfo object and returns if that object does not exist.

If the FileInfo object exists, the program loads its file into a bitmap. Notice that the code uses that object in a using block so it is automatically disposed when the block ends.

Inside the block, the code calls the LabelBitmap method described next to draw the label onto a new bitmap. It is important that the method returns a new bitmap because the using block must not dispose of a bitmap while the program still needs to use it. The program displays the labeled bitmap in the picImage PictureBox.

LabelBitmap

The following LabelBitmap method adds a label to a bitmap and returns the result in a new bitmap.

// Add the label to the image.
private Bitmap LabelBitmap(Bitmap bm)
{
    int label_height = 0;
    try
    {
        label_height = int.Parse(txtMargin.Text);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
        return null;
    }

    Bitmap new_bm = new Bitmap(bm.Width, bm.Height + label_height);
    Rectangle rect = new Rectangle(
        0, bm.Height, bm.Width, label_height);
    using (StringFormat sf = GetStringFormat())
    {
        using (Graphics gr = Graphics.FromImage(new_bm))
        {
            gr.DrawImage(bm, 0, 0);

            using (SolidBrush brush = new SolidBrush(lblSample.BackColor))
            {
                gr.FillRectangle(brush,rect);

                brush.Color = lblSample.ForeColor;
                gr.DrawString(txtMessage.Text, lblSample.Font,
                    brush, rect, sf);
            }
        }
    }

    return new_bm;
}

The method first parses the label area’s desired height. It then makes a new bitmap with extra space added on the bottom for the label.

Next the code creates a rectangle representing the label area.

The program then uses the GetStringFormat method to get a StringFormat object that correctly aligns the message text. I’ll describe that method shortly. The program also makes a Graphics object associated with the new bitmap.

At this point, the code is finally ready to start drawing. It first draws the original image at the origin of the new bitmap. It then uses the background color that you selected to create a brush and fills the label area with that color.

Next the method changes the brush’s color to the selected label color. It then draws the message text into the label area’s rectangle using the StringFormat for alignment.

Note that the DrawString method will wrap the text across multiple lines in the label area if it won’t fit on one line.

The following code shows the GetStringFormat method.

private StringFormat GetStringFormat()
{
    StringFormat sf = new StringFormat();
    if (radUL.Checked || radUM.Checked || radUR.Checked)
        sf.LineAlignment = StringAlignment.Near;
    else if (radML.Checked || radMM.Checked || radMR.Checked)
        sf.LineAlignment = StringAlignment.Center;
    else if (radLL.Checked || radLM.Checked || radLR.Checked)
        sf.LineAlignment = StringAlignment.Far;

    if (radUL.Checked || radML.Checked || radLL.Checked)
        sf.Alignment = StringAlignment.Near;
    else if (radUM.Checked || radMM.Checked || radLM.Checked)
        sf.Alignment = StringAlignment.Center;
    else if (radUR.Checked || radMR.Checked || radLR.Checked)
        sf.Alignment = StringAlignment.Far;
    return sf;
}

This method creates a new StringFormat object. That object has two key alignment properties. The Alignment property determines how text is aligned horizontally (left, center, or right). The LineAlignment property determines how the text is aligned vertically (top, middle, or bottom).

The only weird thing about those properties is that they take values Near, Center, or Far to indicated top/left, center/middle, right/bottom.

This method simply examines the program’s radio buttons to see which is checked, sets the properties appropriately, and returns the StringFormat object.

Conclusion

That concludes the most interesting pieces of code. There are still a few details such as how the program displays the font and color selection dialogs, and how it saves the resulting images. Download the example to see those details and to experiment with the program.

There are many ways that you could enhance this program. For example, you could position the label area in other parts of the image such as 8 pixels from the upper right corner. You could also allow more complicated label backgrounds such as a semi-transparent background placed on top of the image or a gradient background. Handling all of the possibilities would require you to basically write a full drawing program. If you want to try some specific modifications, however, most shouldn’t be too hard.

The previous post explained how to split Hiragana images into left and right pieces. This post explains how to label the right image. My next post will describe the final Hiragana flash card program that uses those images.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Split images into halves in C#

[split images]

Sometimes I starts a project that begins simply enough but that requires me to write a couple of other programs. In this case I wanted to write a simple flash card program to display images of Japanese Hiragana characters so I could learn their pronunciation. The program would display a character. When you clicked or pressed a button, it would show you a second image showing a mnemonic giving the character’s pronunciation.

For example, the picture at the top of the post shows a Hiragana character on the left. The mnemonic image on the right has an A drawn on it to remind you that this character makes the “A” sound as in “car.” (It’s one of the better mnemonics. Some of the others are a bit of a stretch.

I found some images that I liked at ToFuGu, but that site places each character and its mnemonic on a single image. That lead to this program to split images.

Even that wasn’t completely trivial because the two pieces of the image are not centered in their halves. If you split an image in the middle, then the two pieces are near the left and right edges of the halves. For example, if you look at the picture at the top of the post, you’ll see that the two images are near the edges of the picture.

This program lets you split images into halves. It then processes the halves to remove whitespace around the edges so you can displays the results centered.

Using the Program

To use the program, enter the name of the directory that contains the images to split. Also enter an output directory. Use the radio buttons to indicate whether you want to split the images vertically or horizontally, and check the Remove Whitespace box if you like. When you click Split, the program splits the image files in the input directory and saves the results in the output directory with _a and _b appended to the name. For example, the file ku.png is split into files ku_a.png and ku_b.png.

Getting Started

When you click the Split button, the following code executes.

// Process the image files in the input directory,
// saving the results in the output directory.
private void btnSplit_Click(object sender, EventArgs e)
{
    string from_dir, to_dir;
    from_dir = txtFromDir.Text;
    to_dir = txtToDir.Text;
    bool split_horizontally = radSplitHorizontally.Checked;
    bool remove_whitespace = chkRemoveWhitespace.Checked;

    foreach (string filename in Directory.GetFiles(from_dir))
    {
        FileInfo file_info = new FileInfo(filename);
        string extension = file_info.Extension.ToLower();
        if ((extension == ".png") ||
            (extension == ".jpg") ||
            (extension == ".gif") ||
            (extension == ".tiff") ||
            (extension == ".jpeg"))
        {
            SplitFile(file_info, to_dir,
                split_horizontally,
                remove_whitespace);
        }
    }
    picSample.Image = null;
}

This event handler gets the input and output directories, a value indicating which radio button is checked, and a value indicating whether the Remove Whitespace check box it checked. The code then uses Directory.GetFiles to loop over the files in the input directory.

For each file in the directory, the code creates a FileInfo object and examines its extension. If the extension is for a graphical file type, the code calls the SplitFile method described in the next section to split the file.

Split Images

The following SplitFile method splits a file.

// Split the image file into two pieces.
private void SplitFile(FileInfo file_info,
    string to_dir, bool split_horizontally,
    bool remove_whitespace)
{
    Bitmap bm = LoadBitmapUnlocked(file_info.FullName);
    picSample.Image = bm;
    picSample.Refresh();

    int wid = bm.Width / 2;
    int hgt = bm.Height / 2;
    if (split_horizontally)
        hgt = bm.Height;
    else
        wid = bm.Width;

    Rectangle src_rect_a = new Rectangle(0, 0, wid, hgt);
    Bitmap bm_a = new Bitmap(wid, hgt);
    using (Graphics gr = Graphics.FromImage(bm_a))
    {
        gr.DrawImage(bm, 0, 0, src_rect_a,  GraphicsUnit.Pixel);
    }
    if (remove_whitespace) bm_a = RemoveWhitespace(bm_a);
    string filename_a = to_dir + "\\" +
        Path.GetFileNameWithoutExtension(file_info.Name) +
        "_a" + file_info.Extension;
    SaveImage(bm_a, filename_a);

    Rectangle src_rect_b;
    if (split_horizontally)
        src_rect_b = new Rectangle(wid, 0, wid, hgt);
    else
        src_rect_b = new Rectangle(0, hgt, wid, hgt);
    Bitmap bm_b = new Bitmap(wid, hgt);
    using (Graphics gr = Graphics.FromImage(bm_b))
    {
        gr.DrawImage(bm, 0, 0, src_rect_b, GraphicsUnit.Pixel);
    }
    if (remove_whitespace) bm_b = RemoveWhitespace(bm_b);
    string filename_b = to_dir + "\\" +
        Path.GetFileNameWithoutExtension(file_info.Name) +
        "_b" + file_info.Extension;
    SaveImage(bm_b, filename_b);           
}

The method first opens the file without locking it. To see how the LoadBitmapUnlocked method works, see the post Load images without locking their files in C#.

The code displays the image in the picSample PictureBox control. It then gets the image’s dimensions and updates the width or height depending on whether the program should split the image vertically or horizontally.

Next the program creates a new bitmap that is big enough to hold one of the image’s halves. It creates an associated Graphics object and draws the left/top half of the original image onto the new bitmap.

If the method’s remove_whitespace parameter is true, then the code calls the RemoveWhitespace method described later to remove the whitespace around the image.

The code then gets the original file’s name without its extension. It adds “_a” to the name, prepends the output directory, and appends the original extension. For example, this might convert the name C:\Input Directory\ku.png to the name C:\Output Directory\ku_a.png.

Now the code calls the SaveImage method to save the new image into a file with the appropriate format. For example, if the file’s name ends with .jpg, then that method saves the image in JPG format. For information on that method, see the post Save images with an appropriate format depending on the file name’s extension in C#.

The SplitFile method then repeats those steps to generate and save the image on the right/bottom of the original image.

RemoveWhitespace

The following RemoveWhitespace method returns a copy of an image with any whitespace around its edges removed.

// Return a copy of the parts of the bitmap that is not white.
private Bitmap RemoveWhitespace(Bitmap bm)
{
    Rectangle src_rect = NonWhiteBounds(bm);
    Rectangle dest_rect = new Rectangle(0, 0,
        src_rect.Width, src_rect.Height);

    Bitmap new_bm = new Bitmap(src_rect.Width, src_rect.Height);
    using (Graphics gr = Graphics.FromImage(new_bm))
    {
        gr.DrawImage(bm, dest_rect, src_rect, GraphicsUnit.Pixel);
    }
    return new_bm;
}

The method first calls the NonWhiteBounds method described next to find the rectangle in the original image that contains all of its non-white pixels. It creates a bitmap that is the size of that rectangle, makes an associated Graphics object, and draws the corresponding area on the original image onto the new bitmap.

The method then returns the new bitmap.

NonWhiteBounds

The NonWhiteBounds method shown in the following code is somewhat lengthy by relatively straightforward.

// Find the bounds of the image's non-white pixels.
private Rectangle NonWhiteBounds(Bitmap bm)
{
    Bitmap32 bm32 = new Bitmap32(bm);
    bm32.LockBitmap();

    int ymin = bm.Height;
    for (int y = 0; y < bm.Height; y++)
    {
        for (int x = 0; x < bm.Width; x++)
        {
            byte r, g, b, a;
            bm32.GetPixel(x, y, out r, out g, out b, out a);
            if (r + g + b < 765)
            {
                ymin = y;
                break;
            }
        }
        if (ymin < bm.Height) break;
    }
    if (ymin == bm.Height) return new Rectangle(0, 0, -1, -1);

    int ymax = -1;
    for (int y = bm.Height - 1; y >= ymin; y--)
    {
        for (int x = 0; x < bm.Width; x++)
        {
            byte r, g, b, a;
            bm32.GetPixel(x, y, out r, out g, out b, out a);
            if (r + g + b < 765)
            {
                ymax = y;
                break;
            }
        }
        if (ymax > -1) break;
    }

    int xmin = bm.Width - 1;
    for (int x = 0; x < bm.Width; x++)
    {
        for (int y = ymin; y <= ymax; y++)
        {
            byte r, g, b, a;
            bm32.GetPixel(x, y, out r, out g, out b, out a);
            if (r + g + b < 765)
            {
                xmin = x;
                break;
            }
        }
        if (xmin < bm.Width - 1) break;
    }

    int xmax = -1;
    for (int x = bm.Width - 1; x >= xmin; x--)
    {
        for (int y = ymin; y <= ymax; y++)
        {
            byte r, g, b, a;
            bm32.GetPixel(x, y, out r, out g, out b, out a);
            if (r + g + b < 765)
            {
                xmax = x;
                break;
            }
        }
        if (xmax > -1) break;
    }

    bm32.UnlockBitmap();

    return new Rectangle(xmin, ymin,
        xmax - xmin + 1,
        ymax - ymin + 1);
}

This method simply examines the image’s pixels to see which are non-white. You can do that by using the Bitmap class’s GetPixel method, but that method is slow. To make the operation faster, the program uses the Bitmap32 class described in the post Use the Bitmap32 class to manipulate image pixels very quickly in C#. See that post for details.

The NonWhiteBounds method creates a Bitmap32 object associated with the original bitmap. It then locks the Bitmap32 object so it can examine its pixels.

Next the code loops through the image’s rows from top to bottom. For each row, it loops through the row’s pixels from left to right.

When it examines a pixel, the code uses the Bitmap32 class’s GetPixel method to get the pixel’s red, green, blue, and alpha components. Each of those components is a byte with value between 0 and 255. The code adds the red, green, and blue components and compares the result to 765. The value 765 is three times 255, which is what you get if the pixel is completely white. You could modify the program here to ignore pixels that are almost white. For example, you could compare the sum to 755 to ignore pixels that were very close to white.

If the code finds a non-white pixel, the code sets ymin equal to the current row number and breaks out of the inner loop. When it finishes the inner loop, the code checks the ymin value to see if it has been set. If the value is set, the code breaks out of its outer loop. At this point ymin holds the Y coordinate of the first row that contains a non-white pixel.

If the loops do not find any non-white pixels, then ymin keeps its original value, which is the row one pixel beyond the limits of the bitmap. In that case, the method returns a rectangle with negative width and height to indicate that it did not find any non-white pixels.

The method then performs similar steps to find the largest Y coordinate that contains a non-white pixel. Note that ymax could be the same as ymin.

Next the method uses similar loops to find the smallest and largest X coordinates containing non-white pixels. Notice that in these inner loops the value y ranges from ymin to ymax and not over every Y value. We already know that Y values outside of that range contain no non-white pixels, so we don’t need to check them.

After the method has found the minimum and maximum X and Y values, it uses those values to create a rectangle representing the area that contains non-white pixels and returns it.

Conclusion

[split images]

That’s the end of this part of the flash card project. Now we can split the flash card images into two halves containing a Hiragana character an a mnemonic, both with whitespace removed.

Unfortunately some of the mnemonics don’t make much sense until you get used to them. For example, the picture on the right shows one of the mnemonics. From the picture alone, you might guess that this represents the M in Mountain or perhaps the F in Mt Fuji. (This is a Japanese character set, after all.) In fact this mnemonic represents the He in Mt Saint Helens.



[split images]

To make the mnemonics usable until you get used to them, my next post will show how to add labels to the bottoms of images. For example, the picture on the right shows the annotated “he” image.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in graphics, image processing | Tagged , , , , , , , | 2 Comments

Allow scrolling while making a diagonal picture montage in C#


[picture montage]

The post Make an improved diagonal picture montage in C# describes a program that lets you make a diagonal picture montage similar to the one shown above. I recently wanted to make a picture montage that was much bigger. There are two obvious approaches. First, you could scale the montage as you draw it and then save it at a larger scale. Second, you could let the user scroll the montage.

If you take the first approach, then you would need to convert mouse clicks for the scaled result. This is possible, but more work than the second approach, which is practically trivial. This example simply moves the picCanvas PictureBox control that displays the picture montage into a Panel control. That control’s AutoScroll property is set to true, so it automatically displays scroll bars if picCanvas is too large to fit.

That’s all there is to it. Download the example to experiment with it. Look at the code and see the previous post to see additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Find controls by name in WPF with C#

[find controls]

This example lets you find controls by entering their names. When you enter a control’s name and a color and click Apply, the program changes the selected control’s background color.

You can use XAML code to give names to the controls in your application and the C# code behind can refer to the controls by those names. If you want to find a control by its name at run time, however, you have a bit of work to do.

XAML Code

To use the program, you need to know the names of its controls. The program uses the following XAML code. The control names are highlighted in blue so they are easy to see.

<Window x:Class="howto_wpf_find_control_by_name.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="howto_wpf_find_control_by_name"
    Height="220" Width="250"
    Name="MainWindow">
    <Grid Name="Grid1" Margin="5">
        <Grid.RowDefinitions>
            <RowDefinition Height="50" />
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        
        <Grid.Resources>
            <Style TargetType="Button">
                <Setter Property="Width" Value="75" />
                <Setter Property="Height" Value="30"/>
                <Setter Property="VerticalAlignment" Value="Center"/>
                <Setter Property="HorizontalAlignment" Value="Center"/>
                <Setter Property="Margin" Value="10"/>
            </Style>
            <Style TargetType="Label">
                <Setter Property="Width" Value="60" />
                <Setter Property="Margin" Value="5" />
            </Style>
            <Style TargetType="TextBox">
                <Setter Property="Width" Value="75" />
                <Setter Property="VerticalContentAlignment" Value="Center" />
                <Setter Property="Margin" Value="5" />
            </Style>
        </Grid.Resources>

        <Button Name="Button1" Grid.Row="0" Grid.Column="0">Button1</Button>
        <Button Name="Button2" Grid.Row="0" Grid.Column="1">Button2</Button>
        
        <StackPanel Name="StackPanel1"
            Grid.Row="1" Grid.ColumnSpan="2"
            HorizontalAlignment="Center">
            <StackPanel Name="StackPanel2" Orientation="Horizontal">
                <Label Name="Label1">Control:</Label>
                <TextBox Name="TextBox1">TextBox1</TextBox>
            </StackPanel>
            <StackPanel Name="StackPanel3" Orientation="Horizontal">
                <Label Name="Label2">Color:</Label>
                <TextBox Name="TextBox2">Pink</TextBox>
            </StackPanel>
            <Button Name="Button3" Click="Button3_Click" IsDefault="True">Apply</Button> 
        </StackPanel> 
    </Grid>
</Window>

C# Code

The controls in a WPF application form a hierarchy. To find controls by name, you can start at the top of the hierarchy and recursively work your way down through the controls until you find the one that you want. Unfortunately WPF controls don’t provide a simple Children collection that you can examine. Instead you need to use the VisualTreeHelper class’s GetChildrenCount method to see how many children a control has. You then need to us the same class’s GetChild method to get the children.

The following FindDescendant method searches a control hierarchy for a control with the given name.

// Find a descendant control by name.
private static DependencyObject FindDescendant(
    DependencyObject parent, string name)
{
    // See if this object has the target name.
    FrameworkElement element = parent as FrameworkElement;
    if ((element != null) && (element.Name == name)) return parent;

    // Recursively check the children.
    int num_children = VisualTreeHelper.GetChildrenCount(parent);
    for (int i = 0; i < num_children; i++)
    {
        // See if this child has the target name.
        DependencyObject child = VisualTreeHelper.GetChild(parent, i);
        DependencyObject descendant = FindDescendant(child, name);
        if (descendant != null) return descendant;
    }

    // We didn't find a descendant with the target name.
    return null;
}

The method first converts the parent control into a FrameworkElement. If the result is not null (indicating that the parent actually inherits from FrameworkElement) and the element has the desired name, then the method simply returns it.

Notice that the code compares the names as they are. That means, for example, that you cannot search for a control named TextBox1 by entering the name textbox1. You can modify the code to make it case-insensitive if you like.

If the parent control does not have the target name, the program uses the VisualTreeHelper class to see how many children the parent has. It then loops through the children and recursively calls FindDescendant for each.

If one of the recursive calls returns a non-null descendant with the correct name, the method returns it. If none of the recursive calls finds the desired control, the method return null to tell method calls farther up the call stack that the target control is not present in this part of the control hierarchy.

When you click the Apply button, the following code executes.

private void Button3_Click(object sender, RoutedEventArgs e)
{
    // Find the object by name.
    string name = TextBox1.Text;
    DependencyObject descendant = FindDescendant(MainWindow, name);
    if (descendant == null)
    {
        MessageBox.Show("There such control " + name,
            "No Such Control", MessageBoxButton.OK);
        return;
    }

    // Get the color by name.
    string color_name = TextBox2.Text;
    Color color;
    try
    {
        color = (Color)ColorConverter.ConvertFromString(color_name);
    }
    catch
    {
        MessageBox.Show("No such color " + color_name,
            "No Such Color", MessageBoxButton.OK);
        return;
    }

    try
    {
        // Apply the color.
        // See if the object is a Panel.
        if (descendant is Panel)
        {
            Panel panel = descendant as Panel;
            panel.Background = new SolidColorBrush(color);
        }
        else if (descendant is Control)
        {
            Control control = descendant as Control;
            control.Background = new SolidColorBrush(color);
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

This code is actually error handling in case you enter an invalid control name or color.

The code first gets the control name that you entered and calls FindDescendant to find it. The program uses its main window as the top of the control hierarchy to search. If you look back at the way the FindDescendant method works, you’ll see that it starts by checking the parent control to see if it has the target name. That means you can enter MainWindow as a control name and the program will find the top-level window.

If the FindDescendant method returns null, the code displays an error message and returns.

If the method found a control, the program gets the color name that you entered. It then uses the ColorConverter class’s ConvertFromString method to convert that name into a Color. Note that the ConvertFromString method is case-insensitive so LightBlue, lightblue, and LIGHTblue will all work. The color must be a single word and spelling is important, however, so LiteBlue and Light Blue will not work.

If the ConvertFromString method throws an exception, the code displays an error message and returns.

If the program successfully obtains the control and color, it tries to set the control’s background color. To do that, the program determines whether the object is a Panel or Control. If the object is of one of those types, the code converts it into that type and sets its Background property to an appropriate brush.

This is all a bit weird because the object that the program finds is actually a DependencyObject. Unfortunately that class does not have a Background property. Descendant classes such as Panel, Control, TextBlock, and others have the property.

The Panel class is an ancestor of classes such as Grid and StackPanel, which are used by the program. Similarly the Control class is an ancestor of the Label, Button, and TextBox classes used by the program.

The program looks for Panel and Control objects because it uses them. You might need to look for other kinds of controls, such as TextBlock if your program uses them.

Conclusion

Finding controls recursively isn’t new. You can use a similar technique to find controls in Windows Forms applications, too. WPF makes it all a bit more awkward by making you use the VisualTreeHelper class and by placing the Background property in several different classes, but the general approach is similar.

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


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in controls, wpf | Tagged , , , , , , , , , | Leave a comment

Let the user scribble on a background image in C#

[scribble]

The post Let the user scribble on a PictureBox in C# shows how you can let the user draw with the mouse. This example lets you load a background image. It also lets you save the result into an image file.

Loading a Background Image

There are only two basic changes that you need to make to scribble on a background image. First, set the PictureBox control’s Image property to the image. Second, do not clear the control in the Paint event handler. This example also takes a few other actions to make the program more user-friendly.

When you use the File menu’s Load Background Image command, the following code executes.

private void mnuFileLoadBgImage_Click(object sender, EventArgs e)
{
    if (ofdBackground.ShowDialog() == DialogResult.OK)
    {
        picCanvas.SizeMode = PictureBoxSizeMode.AutoSize;
        picCanvas.Anchor =
            AnchorStyles.Top |
            AnchorStyles.Left;

        picCanvas.Image = LoadBitmapUnlocked(ofdBackground.FileName);
    }
}

This code displays a FileSelectionDialog. If you select an image file, the method sets the picCanvas control’s SizeMode property to AutoSize so it resizes to fit its picture. It also sets the control’s Anchor property to Top, Left so the control will not resize when the form resizes.

The code then displays the image in the PictureBox control’s Image property.

For information about the LoadImageUnlocked method, see Load images without locking their files in C#.

Starting a New Scribble

When you invoke the File menu’s New command, the following code executes.

// Start a new drawing.
private void mnuFileNew_Click(object sender, EventArgs e)
{
    Polylines = new List<List<Point>>();
    picCanvas.Image = null;

    int margin = picCanvas.Left;
    int width = ClientSize.Width - 2 * margin;
    int height = ClientSize.Height - picCanvas.Top - margin;
    picCanvas.SizeMode = PictureBoxSizeMode.Normal;
    picCanvas.Size = new Size(width, height);
    picCanvas.Anchor =
        AnchorStyles.Top |
        AnchorStyles.Bottom |
        AnchorStyles.Left |
        AnchorStyles.Right;

    picCanvas.Refresh();
}

This code creates a new Polylines list to remove all of the old scribbles. It then sets the picCanvas control’s SizeMode property to Normal so it can resize when the form resizes. The code also resizes the control so it fills the form nicely. The event handler finishes by setting the control’s Anchor property so it resizes when the form resizes.

Drawing the Scribbles

The following code draws the scribbles.

// Redraw.
private void picCanvas_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

    // Draw the polylines.
    foreach (List<Point> polyline in Polylines)
    {
        e.Graphics.DrawLines(Pens.Black, polyline.ToArray());
    }
}

This code is very similar to the code used by the previous version of the program. The only difference is that this version does not call e.Graphics.Clear before it draws. If it did call Clear, that would erase the image displayed in the background.

As you can see if you run the program, this example doesn’t need the call to Clear. I put it in the previous example mostly out of habit. You would need that call if the Paint event handler draws something that should move or resize when the PictureBox resizes. For example, suppose you want to draw an ellipse that just fills the PictureBox. Then you would need to call Clear before drawing so it can erase any previous ellipses that were drawn when the form had a different size.

Saving the Result

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

private void mnuFileSave_Click(object sender, EventArgs e)
{
    if (sfdResult.ShowDialog() == DialogResult.OK)
    {
        Bitmap bm = GetControlImage(picCanvas);
        SaveImage(bm, sfdResult.FileName);
    }
}

This code displays a SaveFileDialog. If you select a destination file and click Save, the code calls the GetControlImage method to get an image of the PictureBox. It then uses the SaveImage method to save the image with an appropriate file format. For example, if the file name’s extension is png, then SaveImage saves the image as a PNG file.

The following code shows the GetControlImage method.

// Return a Bitmap holding an image of the control.
private Bitmap GetControlImage(Control ctl)
{
    Bitmap bm = new Bitmap(ctl.Width, ctl.Height);
    ctl.DrawToBitmap(bm,
        new Rectangle(0, 0, ctl.Width, ctl.Height));
    return bm;
}

This code makes a bitmap large enough to hold the image. It then calls the control’s DrawToBitmap method to draw the control onto the bitmap and returns the result.

For more information on the SaveImage method, see the post Save images with an appropriate format depending on the file name’s extension in C#.

Conclusion

Displaying an image behind a drawing is fairly easy. Just set the PictureBox control’s Image property and don’t call Clear in the Paint event handler. This is an important technique and a lot of people don’t know about it, but the example demonstrates a couple of other useful techniques such as setting a PictureBox control’s SizeMode and Anchor properties.

Download the example to see additional details and to experiment with the program. For other examples that improve the basic scribble program, see these posts:


Download Example   Follow me on Twitter   RSS feed   Donate




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