Combine image slices in C#


[image slices]

After my recent post Adjust an image’s opacity in C#, I realized that it might be interesting to combine slices from different versions of an image saved with different opacities. I did’t really need to do this; it just seemed like it might be interesting.

And yes, you could make a program that takes a single image and adjusts different slices of it so they have different opacities. This program loads separate files. It assumes that the files are the same size. I don’t think it will crash if they are not, but I won’t guarantee the results will make total sense.

This program performs three main interesting tasks: displaying file names in the list box, arranging the files in the list box, and drawing the image slices.

Displaying File Names

The program should display a file’s name without its complete path. For example, it should list a file as pastries25.png not C:\users\rod\CSharpHelper\examples\whatever\pastries25.png.

However, the program will later need to know the full file paths so it can open the files. We could store the full paths or FileInfo object representing the files in the list box, but then the full path would be displayed.

The program uses the following FileData class to store information for a file.

class FileData
{
    public FileInfo FileInfo = null;
    public Bitmap Picture = null;

    public FileData(string filename)
    {
        FileInfo = new FileInfo(filename);
        Picture = new Bitmap(filename);
    }

    public override string ToString()
    {
        return FileInfo.Name;
    }
}

The class defines a FileInfo object to refer to a file. The Picture field holds the image contained in the file.

The class’s constructor creates a new FileInfo object and saves it in the FileInfo field. It also loads the file’s image and saves it in the Picture field.

The class’s only other piece is its ToString method. This method overrides the default version, which would return the class’s name.

The ListBox class (and the ComboBox class, the Immediate window, and probably some other objects) use an item’s ToString method to decide what to display. Overriding ToString makes the program’s ListBox display the file’s name instead of its path or the class’s name.

When the user selects the File menu’s Open command, the following code executes.

// Open a new file.
private void mnuFileOpen_Click(object sender, EventArgs e)
{
    ofdImage.Multiselect = true;
    if (ofdImage.ShowDialog() == DialogResult.OK)
    {
        foreach (string filename in ofdImage.FileNames)
        {
            FileData file_data = new FileData(filename);
            lstFiles.Items.Add(file_data);
        }

        ClearResult();
    }
}

This code sets the OpenFileDialog control’s Multiselect property to true so the user can select more than one file at a time. The code then calls the dialog’s ShowDialog method to display it.

If the user selects one or more files and clicks Open, the code loops through the dialog’s FileNames collection. It uses each file name to create a FileData object and adds it to the list box. The event handler finishes by calls ClearResult to remove any previously displays image slices.

Displaying the Context Menu

[image slices]

Use the File menu’s Open command to load the files. Note that you can load a file more than once.

If you need to rearrange the files, right-click on a file in the list box and select the Move Up, Move Down, or Delete commands.

To use the menu, I added a ContextMenuStrip named ctxFile to the form at design time. If you click ctxFile in the component tray to select it, then you can use the menu editor on the top of the form to add menu items to it.

The following code shows how the program displays the context menu when you right-click the list box.

// Display the context menu.
private void lstFiles_MouseDown(object sender, MouseEventArgs e)
{
    // If this is not the right button, do nothing.
    if (e.Button != MouseButtons.Right) return;

    // Select the item under the mouse.
    lstFiles.SelectedIndex =
        lstFiles.IndexFromPoint(e.Location);

    // If no item is selected. do nothing.
    if (lstFiles.SelectedIndex == -1) return;

    // Display the context menu.
    ctxFile.Show(lstFiles, e.Location);
}

This code first checks which button you pressed on the list box. If you did not press the right button, the event handler simply exits.

Next, the code uses the ListBox control’s IndexFromPoint method to see which item you clicked in the list box. The code selects the clicked item.

The program then checks to see which item is selected. If you right-clicked on the list box but not on any item, then the selected item will have index -1, meaning no item is selected. In that case, the event handler returns without doing anything.

Finally, if you right-clicked on an item, the code calls the context menu’s Show method to display it.

There’s one more step before the context menu actually appears. When the menu is opening, it executes the following event handler.

// Enable and disable the appropriate context menu items.
private void ctxFile_Opening(object sender, CancelEventArgs e)
{
    ctxMoveUp.Enabled =
        (lstFiles.SelectedIndex > 0);
    ctxMoveDown.Enabled =
        (lstFiles.SelectedIndex < lstFiles.Items.Count - 1);
}

This event handler only enables the Move Up command if the list box’s selected file is not the first one. You can’t move the first file up because it is already at the top of the list.

Similarly the event handler only enables the Move Down command if the selected file is not the last one. You can’t move the last file down because it is already at the bottom of the list.

Note that you could perform those checks in the list box’s MouseDown event handler before displaying the menu. Where you do it is a matter of style. I like this method because it makes the menu enable and disable its own commands.

Note also that the Opening event handler will disable both the Move Up and Move Down menu items if the list box contains a single file and you right-click on it. In that case, the program displays the context menu anyway for two reasons. First, the menu contains a Delete command and that should be available even if the other commands are not.

Second, it’s better to display disabled commands rather than hiding them from the user. That way the user knows where to find the commands; they are just not allowed at this time.

After the Opening event handler finishes, the context menu appears. The following section explains the code that the context menu uses to let you rearrange the files.

Arranging Files

The following event handler executes when you select the Move Up command.

// Move the selected file up in the list.
private void ctxMoveUp_Click(object sender, EventArgs e)
{
    // Get the selected index and its item.
    int index = lstFiles.SelectedIndex;
    if (index < 1) return;
    object item = lstFiles.Items[index];

    lstFiles.Items.RemoveAt(index);
    lstFiles.Items.Insert(index - 1, item);

    ClearResult();
}

This code gets the index of the list box’s currently selected file and uses it to get the corresponding item. The code then removes the selected item and inserts back into the list at the position before its original position. The code finishes by calling the following ClearResult method to remove any previously displayed image slices.

// Clear the result image and disable the Save menu item.
private void ClearResult()
{
    picResult.Image = null;
    picResult.Visible = false;
    mnuFileSave.Enabled = false;
}

This method clears the picResult control’s Image and makes that control invisible. It also disables the File menu’s Save command.

The following code executes when you select the Context menu’s Move Down command.

// Move the selected file down in the list.
private void ctxMoveDown_Click(object sender, EventArgs e)
{
    // Get the selected index and its item.
    int index = lstFiles.SelectedIndex;
    if (index >= lstFiles.Items.Count) return;
    object item = lstFiles.Items[index];

    lstFiles.Items.RemoveAt(index);
    lstFiles.Items.Insert(index + 1, item);

    ClearResult();
}

This event handler is mostly similar to the previous one. It gets the selected item’s index and the item. It removes the item and reinserts it at the next position in the list box. It finishes by calling the ClearResult method to remove any previously displayed image slices.

The following code shows how the Delete command removes a file from the list box.

// Remove this file from the list.
private void ctxDelete_Click(object sender, EventArgs e)
{
    lstFiles.Items.RemoveAt(lstFiles.SelectedIndex);
    ClearResult();
}

This code simply removes the selected item from the list box and calls ClearResult.

Drawing Image Slices

The following ShowImage method displays the image slices.

// Display the sliced image.
private void ShowImage()
{
    // Find the biggest image size.
    int width = 0;
    int height = 0;
    foreach (FileData file_data in lstFiles.Items)
    {
        if (width < file_data.Picture.Width)
            width = file_data.Picture.Width;
        if (height < file_data.Picture.Height)
            height = file_data.Picture.Height;
    }

    // Get the number of slices and the slice width.
    int num_slices = lstFiles.Items.Count;
    int slice_width = width / num_slices;

    // Make the result image.
    ResultImage = new Bitmap(width, height);
    using (Graphics gr = Graphics.FromImage(ResultImage))
    {
        gr.Clear(picResult.BackColor);

        // Draw the slices.
        for (int i = 0; i < num_slices; i++)
        {
            int x = i * slice_width;
            FileData file_data = (FileData)lstFiles.Items[i];
            Rectangle rect = new Rectangle(x, 0, width, height);
            gr.DrawImage(file_data.Picture,
                rect, rect, GraphicsUnit.Pixel);
        }
    }

    // Display the result.
    picResult.Image = ResultImage;
    picResult.Visible = true;
    mnuFileSave.Enabled = true;
}

This method first loops through the list box’s FileData items and saves the maximum of their widths and heights. It sets num_slices equal to the number of files and sets slice_width equal to the maximum image width divided by the number of slices.

Next, the code creates a result bitmap that is large enough to hold the maximum width and height. It creates an associated Graphics object and loops through the slices. It gets the FileData object corresponding to each slice, makes a Rectangle that defines the slice’s part of the picture, and then uses the Graphics object’s DrawImage method to copy that part of the current file onto the result bitmap.

Conclusion

If, as in my example, the images are all the same image saved with different opacities, then the slices line up to give you the slice effect. If the images are unrelated, the result may be strange. Download the example to see additional details.

You may never need to generate image slices. Even if you don’t, this example shows a useful technique for allowing the user to arrange the items in a ListBox.


Download Example   Follow me on Twitter   RSS feed   Donate




About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.
This entry was posted in graphics, image processing and tagged , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.