Draw random rectangles in C#

[random rectangles]

This example simply draws random rectangles on top of each other. It’s mostly straightforward, although it does contain a couple of techniques that are useful if you do a lot of graphics programming.


When it starts, the following code performs a few initialization tasks.

private Bitmap Bm;
private Graphics Gr;

// Make a bitmap to display.
private void Form1_Load(object sender, EventArgs e)
{
    FormBorderStyle = FormBorderStyle.FixedDialog;
    MinimizeBox = false;
    MaximizeBox = false;
    DoubleBuffered = true;
    // Set at design time:
    //      StartPosition = FormStartPosition.CenterScreen;

    Bm = new Bitmap(ClientSize.Width, ClientSize.Height);
    Gr = Graphics.FromImage(Bm);
    BackgroundImage = Bm;
}

This code sets the form’s BorderStyle property to FixedDialog so the user cannot resize it. If the user resized the form, the program would need to worry about drawing on an area that can change size, and that would be more work so I skipped it.

The code also sets the form’s MinimizeBox and MaximizeBox properties to false so the user cannot minimize and maximize the form, respectively. The form’s border style is FixedDialog, so these properties make me wonder, “What part of, ‘do not allow the user to resize the form,’ don’t you understand?”

In this program, if the user minimizes the form, then it has width and height zero. That makes a later calculation will pass a negative value into the Random object’s Next method and that crashes the program. Removing the MinimizeBox prevents that.

If the user maximizes the form, then the Bitmap (described shortly) doesn’t fill the entire form so it is tiled. That’s kind of interesting, but it isn’t what I had in ind and it seems to noticeably hurt performance, so I prevent that, also.

The code also sets the form’s DoubleBuffered property to true. That tells the form to draw its graphics in a secondary piece of memory and only display it when the drawing is complete. This is important for programs that draw a lot of graphics very quickly. If you don’t set this property to true, the program flickers wildly. (Comment out that statement to see. It’s pretty annoying.)

You can set the form’s StartPosition property in the code, but by the time the Load event handler executes, the form has already been positioned, so that has no effect. If you want to change the form’s startup position, you must do it at design time.

Next, the program creates a Bitmap to fit the form’s client area. It also creates an associated Graphics object on which to draw. Finally, it sets the form’s background image to the Bitmap so it will display anything that the program draws on the Bitmap (via the Graphics object).

At design time, I added a Timer that executes the following Tick event handler every 100 milliseconds. (Ten times per second).

private Random Rand = new Random();

private void tmrMakeRectangle_Tick(object sender, EventArgs e)
{
    int x = Rand.Next(ClientSize.Width - 10);
    int y = Rand.Next(ClientSize.Height - 10);
    int width = Rand.Next(ClientSize.Width - x);
    int height = Rand.Next(ClientSize.Height - y);
    Color color = Color.FromArgb(128,
        255 * Rand.Next(2),
        255 * Rand.Next(2),
        255 * Rand.Next(2));
    using (Brush brush = new SolidBrush(color))
    {
        Gr.FillRectangle(brush, x, y, width, height);
    }
    Refresh();
}

The Rand object is declared at the class level so it is created only once and then every call to the event handler uses the same object. That is important. If the event handler created its own Random object, then that object would use the system time to initialize itself. If the event handler executes quickly enough, then some of its executions would use the same time to initialize the object so they would generate the same “random” numbers. that would mean the program would waste time generating the same rectangles multiple times.

This program doesn’t run fast enough to generate too many duplicate rectangles if you create Random objects in that way, but it’s better to use a single Random object if multiple methods will be generating random numbers very quickly. That avoid possible duplication and saves a little time creating, initializing, and destroying the objects.

The Tick event handler uses the Random object to pick X and Y coordinates for a rectangle’s upper left corner. It leaves some space to the right and blow the possible X and Y coordinates so there is room for the rectangle to have a non-zero width and height.

The code then generates a random width and height and a random color. Each off the color’s red, green, and blue components is either 0 or 255. That means the possible colors include:

  • Black (all 0)
  • White (all 255)
  • The primary colors red, green, and blue (one component is 255 and the others are 0)
  • The secondary colors yellow, cyan, and fuchsia (two components are 255 and one is 0)

The color’s alpha (opacity) component is 128, so the color is semi-transparent and rectangles behind other rectangles show through.

After generating the random color, the program fills the random rectangle with it. The form already displays the bitmap Bm as its background image, so the code simply refreshes the form to display the result.

The only other piece to the program is the following KeyDown event handler.

private void Form1_KeyDown(object sender, KeyEventArgs e)
{
    if (e.KeyCode == Keys.Escape) Gr.Clear(BackColor);
}

If you press Escape, this event handler clears the form’s background so the drawing starts over.

Download the example and experiment with it. You might try making it draw ellipses instead of rectangles. Or try changing the way the random colors are generated. The program is amazingly simple but produces an interesting result.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Save a bitmap showing user drawn line segments in C#

[save a bitmap]

The example Draw, move, and delete line segments in C# lets the user draw line segments, but it doesn’t include a way to save a bitmap showing the result. This example provides the same features as the previous one, plus it allows you to save a bitmap showing the lines that you drew. When you select the File menu’s Save As command, the program displays a SaveFileDialog. If you select a png, bmp, or jpg file, the program draws the line segments onto a bitmap and saves it in the file that you selected.

When you select the Save As menu item, the following event handler executes.

// Save the drawing.
private void mnuFileSave_Click(object sender, EventArgs e)
{
    if (sfdPicture.ShowDialog() != DialogResult.OK)
        return;

    // Make a bitmap that fits the PictureBox.
    Bitmap bm = new Bitmap(
        picCanvas.ClientSize.Width,
        picCanvas.ClientSize.Height);

    // Draw.
    using (Graphics gr = Graphics.FromImage(bm))
    {
        gr.SmoothingMode = SmoothingMode.AntiAlias;
        for (int i = 0; i < Pt1.Count; i++)
            gr.DrawLine(Pens.Blue, Pt1[i], Pt2[i]);
    }

    // Save the result.
    SaveImage(bm, sfdPicture.FileName);
}

This code first displays the SaveFileDialog. If you close the dialog without selecting a file, the event handler exits.

If you do select a file, the code creates a Bitmap that has the same size as the client area of the program’s PictureBox.
If makes a Graphics object associated with the bitmap and sets its SmoothingMode property to draw smooth lines. The code then loops through the lines that you drew and draws them on the Graphics object.

The code finishes by calling the SaveImage method to save the bitmap in the format that is appropriate for the file’s name. For example, if the file’s name ends with .png, then the method saves the bitmap with the PNG file format. See the post Save images with an appropriate format depending on the file name’s extension in C# for information about the SaveImage method.

Note that the saved bitmap has the same size as the client area of the program’s PictureBox. That means any parts of lines that lie outside of the PictureBox control’s client area will not fit on the bitmap. If you need to save segments that lie outside of that area, you can modify the code to use a larger bitmap. For example, you could loop through all of the line segments and size the bitmap so it is big enough to hold all of the segments’ end points. You could also translate the drawing if some of the end points have negative X or Y coordinates.

Download the example to see additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Give an image an improved transparent background in C#


[transparent background]

This example lets you make some pixels in an image part of a transparent background. Use the File menu to open an image file. When you click on the original image on the left, the program converts pixels that have the same color as the one you clicked into transparent pixels. After you have made areas of the image transparent, click Expand Transparency to blend the edges of the transparent background into the adjacent non-transparent pixels.

The following sections explain the key parts of the program.

Overview

The Bitmap class’s MakeTransparent method to give an image a transparent background, but it has some major disadvantages. First, the method only affects pixels of the exact color specified and won’t change pixels that are close to that color even if they should also be part of the transparent background. Take a look at the middle picture at the top of this post. Many pixels that look white around the edges of the logo are not transparent even though they should be part of the transparent background. Those pixels are not exactly white, so when I clicked on a white pixel in the picture on the left, those pixels were not changed. This is a particular problem with JPG and other image formats that may slightly alter pixel colors to save space.

The MakeTransparent method also makes all pixels of a given color transparent, even if they should not be part of the transparent background. If you look carefully at the middle picture, you can see that some of the pixels in the chef’s hat and in the heart in “Oven” have been made transparent. Those pixels are not outside of the logo so they should not be part of the transparent background, but they were white so the method made them transparent.

This example uses a different method to handle those problems. It performs two main tasks: making pixels transparent and expanding the transparent background.

Making Pixels Transparent

The MakeTransparent method makes all pixels of a given color transparent. It changes pixels even if they are not in a contiguous region and it doesn’t change pixels if they are even the tiniest bit different from the target color.

The following Transparentify method colors pixels that match a color approximately and that are contiguous to a given pixel.

// Make the indicated pixel's color transparent.
private Bitmap Transparentify(Bitmap bm_input,
    int x, int y, int dr, int dg, int db)
{
    // Get the target color's components.
    Color target_color = bm_input.GetPixel(x, y);
    byte r = target_color.R;
    byte g = target_color.G;
    byte b = target_color.B;

    // Make a copy of the original bitmap.
    Bitmap bm = new Bitmap(bm_input);

    // Make a stack of points that we need to visit.
    Stack<Point> points = new Stack<Point>();

    // Make an array to keep track of where we've been.
    int width = bm_input.Width;
    int height = bm_input.Height;
    bool[,] added_to_stack = new bool[width, height];

    // Start at the target point.
    points.Push(new Point(x, y));
    added_to_stack[x, y] = true;
    bm.SetPixel(x, y, Color.Transparent);

    // Repeat until the stack is empty.
    while (points.Count > 0)
    {
        // Process the top point.
        Point point = points.Pop();

        // Examine its neighbors.
        for (int i = point.X - 1; i <= point.X + 1; i++)
        {
            for (int j = point.Y - 1; j <= point.Y + 1; j++)
            {
                // If the point (i, j) is outside
                // of the bitmap, skip it.
                if ((i < 0) || (i >= width) ||
                    (j < 0) || (j >= height)) continue;

                // If we have already considred
                // this point, skip it.
                if (added_to_stack[i, j]) continue;

                // Get this point's color.
                Color color = bm_input.GetPixel(i, j);

                // See if this point's RGB vlues are with
                // the allowed ranges.
                if (Math.Abs(r - color.R) > dr) continue;
                if (Math.Abs(g - color.G) > dg) continue;
                if (Math.Abs(b - color.B) > db) continue;

                // Add the point to the stack.
                points.Push(new Point(i, j));
                added_to_stack[i, j] = true;
                bm.SetPixel(i, j, Color.Transparent);
            }
        }
    }

    // Return the new bitmap.
    return bm;
}

The method first gets the color of the target pixel at position [x, y]. It then gets the color’s red, green, and blue components.

Next, the code creates a bitmap named bm to hold its final result. It then creates a Stack of Point object to keep track of the pixels that it needs to visit. It also makes a Boolean array added_to_stack to keep track of the pixels that have previously been added to the stack. The code pushes the starting point onto the stack, sets its added_to_stack value to true, and makes it transparent.

The method then enters a loop that executes until the stack is empty. Within the loop, the code gets the first point from the stack. It then loops through that point’s neighboring pixels. The code checks whether the neighboring pixel:

  • Lies within the image
  • Has not already been added to the stack
  • Has red, green and blue color components that are close to those of the target pixel’s color

If the neighbor meets all of those requirements, then the code adds the neighbor to the stack, sets its added_to_stack value to true, and makes the neighbor transparent.

The method continues processing the stack until it has processed all of the pixels that are reachable from the initial target pixel and that have acceptable colors. After it finishes processing all of those pixels, the method returns the result bitmap.

Expanding the Transparent Background

The following picture shows the sample image after I clicked on one of the pixels in the white outer area on the original image.


[transparent background]

The result has made most of the appropriate pixels part of the transparent background. The pixels nearest to the edges of the logo are still not transparent because they differed from the target color by too much. They are mostly white, but not white enough.

Notice that the white pixels inside the chef’s hat and the heart in “Oven” were not converted into transparent background pixels. They were not reachable from the target point that I clicked, so they keep their original white color.

One problem with the result so far is those almost-white pixels near the edge of the logo. If you draw the result on a white a background, it blends smoothly into the background and produces a nice result. Unfortunately, it you draw the logo on top of some other background, such as the blue and yellow background shown here, the almost-white pixels are clearly visible.

The next step is to expand the transparent background so the almost-white pixels blend into the non-transparent pixels that are next to them. The example program uses the following ExpandTransparency method to do that.

// Make pixels that are near transparent ones partly transparent.
private Bitmap ExpandTransparency(Bitmap input_bm, float max_dist)
{
    Bitmap result_bm = new Bitmap(input_bm);

    float[,] distances =
        GetDistancesToTransparent(input_bm, max_dist);
    
    int width = input_bm.Width;
    int height = input_bm.Height;
    for (int x = 0; x < width; x++)
    {
        for (int y = 0; y < height; y++)
        {
            // If this pixel is transparent, skip it.
            if (input_bm.GetPixel(x, y).A == 0)
                continue;

            // See if this pixel is near a transparent one.
            float distance = distances[x, y];
            if (distance > max_dist) continue;
            float scale = distance / max_dist;

            Color color = input_bm.GetPixel(x, y);
            int r = color.R;
            int g = color.G;
            int b = color.B;

            int a = (int)(255 * scale);
            color = Color.FromArgb(a, r, g, b);
            result_bm.SetPixel(x, y, color);
        }
    }
    return result_bm;
}

This method first creates a copy of the input bitmap. It then calls the GetDistancesToTransparent method described shortly to find the distance from each pixel to the transparent pixel that is closest to it.

The code then loops through the pixels in the image. If the current pixel is already transparent, the loop skips it. The program also skips the pixel if the distance to the nearest transparent pixel is greater than the maximum distance max_dist.

The idea for the remaining pixels is to scale the pixel’s alpha (opacity) component so those that are closest to a transparent pixel are mostly transparent. To do get a pixel’s scale, the code divides the distance from the pixel to a transparent pixel by the maximum distance that we care about. The code sets the pixel’s alpha component to that scale times the maximum possible component value 255.

For example, suppose dx and dy are five as shown in the pictures above. Then max_dist is dx + dy = 10.0.

Now suppose a particular pixel is one pixel away from a transparent background pixel. In that case, the scale is 1 / 10.0 = 0.1. The code sets that pixel’s alpha component to 0.1 * 255 = 25, so the pixel is mostly transparent. The pixel is close to a transparent pixel, so that makes sense.

For another example, consider a pixel that is nine pixels away from a transparent pixel. In that case, the scale factor is 9 / 10.0 = 0.9, so the code sets its alpha component to 0.9 * 255 = 229. This pixel is far from the transparent pixel, so it is mostly opaque.

After it has finished processing all of the image’s pixels, the method returns the result bitmap.

Handling Edges

This method is pretty good at smoothing the edges of large areas of transparent pixels, but it does not treat the edges of the image as transparent. For example, consider the picture on the right in the following figure. (It’s the same as the previous one.)


[transparent background]

The pixels along the edge of the logo are almost white. Now suppose the edges of the image come right up to those almost-white pixels. In that case, the almost white pixels along the edges of the image are adjacent to the image’s edges, but they are not adjacent to any transparent pixels. That means their opacities will not be adjusted.

You could modify the code to treat the edges of the image as transparent. That would work, but what if you don’t want to treat all of the edges as transparent?

An alternative approach is to ensure that the image has a border of at least one transparent pixel along the edges that you want to blend. That’s what I’ve done in this example. The example image has a thin border of white pixels along its edges so the first step can make them transparent.

GetDistancesToTransparent

The following GetDistancesToTransparent method builds an array giving the distances from each pixel in the image to a transparent pixel.

// Return an array showing how far each
// pixel is from a transparent one.
private float[,] GetDistancesToTransparent(
    Bitmap bm, float max_dist)
{
    int width = bm.Width;
    int height = bm.Height;
    float[,] distances = new float[width, height];
    for (int x = 0; x < width; x++)
        for (int y = 0; y < height; y++)
            distances[x, y] = float.PositiveInfinity;

    // Examine pixels.
    int dxmax = (int)max_dist;
    if (dxmax < max_dist) dxmax++;
    for (int x = 0; x < width; x++)
    {
        for (int y = 0; y < height; y++)
        {
            // See if this pixel is transparent.
            if (bm.GetPixel(x, y).A == 0)
            {
                for (int dx = -dxmax; dx <= dxmax; dx++)
                {
                    int px = x + dx;
                    if ((px < 0) || (px >= width)) continue;
                    for (int dy = -dxmax; dy <= dxmax; dy++)
                    {
                        int py = y + dy;
                        if ((py < 0) || (py >= height)) continue;
                        float dist = (float)Math.Sqrt(dx * dx + dy * dy);
                        if (distances[px, py] > dist)
                            distances[px, py] = dist;
                    }
                }
            }
        }
    }
    return distances;
}

The method first creates an array to hold the distances and initializes it so every entry is float.PositiveInfinity. It then loops through all of the image’s pixels.

If a pixel is transparent, the code loops through that pixel’s neighbors. The code calculates the distance between the transparent pixel and its neighbor. If the calculated distance is less than the neighbor’s current distance in the distances array, then the code updates the array.

Summary

This program probably isn’t perfect, but it’s a lot better than the MakeTransparent method alone. It only makes pixels transparent that are connected to an initial starting pixel. It also catches pixels that are close to but not exactly the same as the initial pixel. Finally, it allows you to expand from transparent pixels into the adjacent pixels to blend the image’s transparent background so it will sit nicely on any other background where you draw it.

Download the example program to give it a try and to see additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Make an image with rounded corners and a transparent background in C#

[rounded corners]

Recently I wanted to make an image for a gift card that had rounded corners. Unfortunately the Graphics class does not have a method to draw rectangles with rounded corners. Fortunately, I have already described the key method in my earlier post Draw rounded rectangles in C#. That post describes a MakeRoundedRect method that creates a GraphicsPath representing a rectangle that has rounded corners. You can then draw or fill it as needed.

This example uses the following code to create and display an image with rounded corners and a transparent background.

// Make and display the image with rounded corners.
private void ShowImage()
{
    // If the corners are not rounded,
    // just use the original image.
    if ((scrXRadius.Value == 0) || (scrYRadius.Value == 0))
    {
        picImage.Image = OriginalImage;
        return;
    }

    // Make a bitmap of the proper size.
    int width = OriginalImage.Width;
    int height = OriginalImage.Height;
    Bitmap bm = new Bitmap(width, height);
    using (Graphics gr = Graphics.FromImage(bm))
    {
        // Clear with a transparent background.
        gr.Clear(Color.Transparent);
        gr.SmoothingMode = SmoothingMode.AntiAlias;
        gr.InterpolationMode = InterpolationMode.High;

        // Make the rounded rectangle path.
        GraphicsPath path = MakeRoundedRect(
            new Rectangle(0, 0, width, height),
            scrXRadius.Value, scrYRadius.Value,
            true, true, true, true);

        // Fill with the original image.
        using (TextureBrush brush = new TextureBrush(OriginalImage))
        {
            gr.FillPath(brush, path);
        }
    }
    picImage.Image = bm;
}

As you can probably guess, the original image is stored in the variable OriginalImage.

The scrXRadius and scrYRadius scroll bars let the user select the radii used to draw the rounded corners. If either the X or Y radii of the rectangle’s rounded corners are zero, then the result should not have rounded corners. In that case, the method simply displays the original image and returns.

If the X and Y radii are not zero, the method gets the image’s dimensions, makes a Bitmap of the same size, and creates an associated Graphics object. It clears the bitmap with the color Transparent and sets the InterpolationMode to AntiAliass so any shapes that it draws have smooth, anti-aliased edges.

Next, the code calls the MakeRoundedRect method to get a path representing a rectangle with rounded corners. The method then creates a TextureBrush that fills with copies of the original image. It uses the brush to fill the path that defines the rounded rectangle.

The code finishes by displaying the new image in the picImage PictureBox.

Download the example and see the earlier post to learn about additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Add a watermark to all of the files in a directory in C#


[example]

I recently wanted to make a slide show where each picture displayed a logo or watermark. File Explorer can easily play a slide show. Simply follow these steps:

  1. Browse to the folder containing the images.
  2. Click one of the image files.
  3. On File Explorer’s ribbon, open the Picture Tools tab and click Slide Show.

While the slide show is running, right-click on an image to change options such as speed and whether the show should shuffle the images instead of playing them in alphabetical order.

That works pretty well. I just needed to add watermarks to all of the images in a directory.

You could write a program to simply open the files and add the watermarks, but there’s a catch. The slide show resizes the images so they are as large as possible without distorting them. That means images many be resized by different amounts to make them fill the screen. If you add all of the watermarks at the same size, then they will appear at different sizes in the slide show.

So I wrote a slightly different program that lets you specify how big your screen is. (The program could just look it up, but I wanted to let you prepare for a slide show with different resolution if necessary.) It then loops through the files, calculates the amount by which the file will be scaled during the slide show, and draws the watermark so it has a desired size when scaled.

That all worked, more or less, but the directory I wanted to process was fairly large so processing it would take some time. To let you know that the program was still running, I wanted the program to show you the names of the files processed and the updated images as they were produced. That worked for a while, but after about 10 or 12 pictures, the program froze and stopped displaying the revised files and no amount of calls to Refresh seemed to help.

The way around this is to use a BackgroundWorker. I don’t use BackgroundWorker much (I suspect few people do), so this seemed like a good time to show how to use it.

There are three main parts to using a BackgroundWorker, so I’ll describe them in the following sections.

Setup

A BackgroundWorker performs work on a separate thread. If you’ve worked with multiple threads before, you know that only the user interface (UI) thread can modify the user interface. The means the BackgroundWorker cannot update the user interface to show its progress.

In order to provide feedback to the user, the BackgroundWorker can raise its ProgressChanged event. The event handler runs on the UI thread, so it can manipulate the user interface to provide feedback. The worker will never raise that event, however, unless you set its WorkerReportsProgress property to true. Forgetting to set that property is a common and frustrating bug. The code is all correct, but the event doesn’t fire because you forgot to set that property at design time. (You can set it in the form’s Load event handler if you prefer.)

This example may take a while to process a large directory, so I also wanted to allow the user to cancel the process. You can tell a BackgroundWorker to stop running, but it won’t stop unless you set its WorkerSupportsCancellation property to true. This is another common and confusing bug. If you forget to set that property, the code looks correct (because it is), but the program cannot stop the BackgroundWorker.

To summarize the setup steps:

  • Create the BackgroundWorker component.
  • If you want the worker to provide progress feedback, set its WorkerReportsProgress property to true.
  • If you want to be able to cancel the worker, set its WorkerSupportsCancellation property to true.

After you finish the setup, the BackgroundWorker uses three event handlers to manage its work. The following sections describe those event handlers.

Starting and Stopping

When you click the program’s Process Files button, the following code starts or stops the BackgroundWorker.

// Start or stop adding watermarks.
private void btnProcessFiles_Click(object sender, EventArgs e)
{
    if (btnProcessFiles.Text == "Process Files")
    {
        // Launch the BackgroundWorker.
        btnProcessFiles.Text = "Stop";
        Cursor = Cursors.WaitCursor;
        bwProcessImage.RunWorkerAsync();
    }
    else
    {
        // Stop.
        btnProcessFiles.Text = "Process Files";
        bwProcessImage.CancelAsync();
    }
}

If the button’s caption is “Process Files,” then the worker is not running. The program changes the button’s caption to “Stop” and calls the worker’s RunWorkerAsync method to make it start running. This makes the worker raise its DoWork event handler, which is described in the next section.

If the button’s caption is “Stop,” then the worker is already running. In that case, the program changes the button’s caption to “Process Files” and calls the worker’s CancelAsync method to tell it to stop running. You’ll see in the next section how that stops the worker.

DoWork

When you call the worker’s RunWorkerAsync method, it raises its DoWork event. The following code shows the event handler used by this example.

// Add the watermark to the files in the background.
private int NumProcessed = 0;
private void bwProcessImage_DoWork(object sender, DoWorkEventArgs e)
{
    // Get parameters.
    int file_width = int.Parse(txtImageWidth.Text);
    int file_height = int.Parse(txtImageHeight.Text);
    int wm_width = int.Parse(txtWatermarkWidth.Text);
    int wm_height = int.Parse(txtWatermarkHeight.Text);
    int xmargin = int.Parse(txtMarginX.Text);
    int ymargin = int.Parse(txtMarginY.Text);
    float opacity = float.Parse(txtOpacity.Text);

    string output_path = txtOutput.Text;
    if (!output_path.EndsWith("\\")) output_path += "\\";

    // Adjust the watermark's opacity.
    Bitmap wm = SetOpacity(Watermark, opacity);

    // Get the watermark's input rectangle.
    RectangleF source_rect = new RectangleF(
        0, 0, wm.Width, wm.Height);

    // Loop through the files.
    NumProcessed = 0;
    FileInfo[] file_infos = null;
    try
    {
        DirectoryInfo input_dir_info = new DirectoryInfo(txtInput.Text);
        file_infos = input_dir_info.GetFiles();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
        e.Cancel = true;
        return;
    }

    foreach (FileInfo input_file_info in file_infos)
    {
        string filename = Path.Combine(output_path,
            input_file_info.Name);

        Bitmap bm = null;
        try
        {
            // Load the input file.
            bm = new Bitmap(input_file_info.FullName);

            // Get the scale.
            float xscale = file_width / (float)bm.Width;
            float yscale = file_height / (float)bm.Height;
            float scale = Math.Min(xscale, yscale);

            // Make a destination rectangle so the watermark
            // has the desired size when the image is scaled.
            RectangleF dest_rect = new RectangleF(
                xmargin, ymargin,
                wm_width / scale, wm_height / scale);

            // Draw the watermark on the image.
            using (Graphics gr = Graphics.FromImage(bm))
            {
                gr.InterpolationMode =
                    InterpolationMode.HighQualityBicubic;
                gr.DrawImage(wm, dest_rect, source_rect,
                    GraphicsUnit.Pixel);
            }

            // Save the result.
            SaveImage(bm, filename);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Skipped {0}. {1}",
                input_file_info.Name, ex.Message);
        }

        // Show progress.
        NumProcessed++;
        int percent_complete =
            (100 * NumProcessed) / file_infos.Length;
        Progress progress = new Progress(bm, filename);
        bwProcessImage.ReportProgress(
            percent_complete, progress);

        // See if we should cancel.
        if (bwProcessImage.CancellationPending)
        {
            e.Cancel = true;
            break;
        }
    }
}

This code gets the input parameters that the user entered in the form’s text boxes. It then uses the SetOpacity method to adjust the watermark image’s opacity. (For information about that method, see my post Adjust an image’s opacity in C#.)

Next, the code creates a Rectangle that represents the watermark’s area. This will be the area that the program copies onto the images.

The code then creates a DirectoryInfo object representing the input directory and uses its GetFiles method to get an array of FileInfo objects that represent the directory’s files. It performs those steps inside a try catch block in case the directory doesn’t exist.

The program then loops through the directory’s files. It combines the file’s name (without the path) and the output directory to get the name of the output file.

Next, the event handler loads the image file into a Bitmap. It does this inside a try catch block in case the file is not an image file. If the code successfully loads the file, it calculates the amounts by which it could scale the image to fill the desired screen dimensions horizontally and vertically. The smaller of the two scales is the amount by which the image will actually be scaled during the slide show.

The code divides the desired watermark width and height by the scale to get the size that the watermark should have so it is scaled to its desired size. It then uses those dimensions to create a destination Rectangle.

Now the program finally does some drawing. It creates a Graphics object associated with the file’s Bitmap and draws the watermark onto it in the destination rectangle.

The program then calls the SaveImage method to save the modified image into the output file. (For information on the SaveImage method, see my post Save images with an appropriate format depending on the file name’s extension in C#.)

After it has processed a file, or failed to process it if it is not an image file, the worker reports its progress to the UI thread. To do that, it calculates its completion percentage. It also creates a Progress object to pass information to the UI thread. The following code shows the Progress class.

class Progress
{
    public Bitmap Image;
    public string Filename;

    public Progress(Bitmap image, string filename)
    {
        Image = image;
        Filename = filename;
    }
}

This class simply holds an image and a filename. The DoWork event handler places the name of the file just processed and its new image in a Progress object. It then calls the worker’s ReportProgress method passing it the completion percentage and the Progress object. The ReportProgress method makes the worker fire the ProgressChanged event described in the next section.

The final thing that the DoWork event handler must do is check to see if the worker has been told to stop. It does that by checking the worker’s CancellationPending property. If that property is true, then the worker has been told to stop. In that case, the DoWork event handler should stop doing whatever it is doing. In this example, that means it should break out of its file-processing loop.

The code also sets the event handler’s e.Cancel parameter to true to indicate that the event handler was canceled and did not finish all of its work.

ProgressChanged

When the DoWork event handler calls the worker’s ReportProgress method, the following event handler executes on the UI thread.

// Update the progress bar.
private void bwProcessImage_ProgressChanged(
    object sender, ProgressChangedEventArgs e)
{
    prgFiles.Value = e.ProgressPercentage;
    Progress progress = (Progress)e.UserState;
    lblFile.Text = progress.Filename;
    picWatermark.Image = progress.Image;
    picWatermark.Refresh();
    Console.WriteLine("Saved file " + progress.Filename);
}

This event handler sets the prgFiles progress bar’s Value property to indicate the completion percentage.

The event handler’s e.UserState parameter contains whatever object the DoWork event handler passed into the ReportProgress method as its second parameter. (That parameter is optional, in case you only want to pass the completion percentage to the ProgressChanged event handler.)

The code converts e.UserState from a generic object to a Progress object. It then displays the processed file’s name in the lblFile label. It also displays the newly watermarked image in the picWatermark PictureBox. Finally, the event handler writes the name of the modified file in the Output window.

RunWorkerCompleted

When the worker finishes, it fires its RunWorkerCompleted event so its event handler can take any action that is needed to clean up. This example uses the following event handler.

// Clean up.
private void bwProcessImage_RunWorkerCompleted(
    object sender, RunWorkerCompletedEventArgs e)
{
    prgFiles.Value = 0;
    lblFile.Text = "";
    picWatermark.Image = Watermark;
    Cursor = Cursors.Default;
    MessageBox.Show("Processed " +
        NumProcessed.ToString() + " files");
    .Text = "Process Files";
}

This code clears the progress bar and lblFile label. It displays the original watermark image in the picWatermark PictureBox, resets the form’s cursor to the default, and displays a message box indicating the number of files that the program processed. Finally, it sets the btnProcessFiles button’s caption to “Process Files.”

At this point, the BackgroundWorker stops and the program is ready to start the whole process over again.

Conclusion

The BackgroundWorker is somewhat complicated, but it can be handy when you want to perform a long sequence of tasks. Here are the basic steps.

  • Create the BackgroundWorker component.
  • If you want the worker to provide progress feedback, set its WorkerReportsProgress property to true.
  • If you want to be able to cancel the worker, set its WorkerSupportsCancellation property to true.
  • Call the worker’s RunWorkerAsync method to make it start working.
  • The DoWork event handler performs the work.
    • The DoWork method can occasionally call the worker’s ReportProgress method to report progress. That causes the ProgressChanged event handler to execute on the UI thread to display progress information such as by updating a progress bar.
    • The DoWork method should check the worker’s CancellationPending property to see if it should stop. If CancellationPending is true, then the DoWork event handler should set e.Cancel to true and stop working.
  • The UI thread can call the worker’s CancelAsync method to tell the worker to stop. (That sets CancellationPending to true.)
  • When DoWork finishes, either because it completed its work or because it was canceled, the worker fires its RunWorkerCompleted event. The event handler runs on the UI thread and can perform cleanup tasks.

This example performs a few other chores such as allowing the user to load a new watermark or browse for the input and output directories. Download the example to see additional details.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Center text above or below a line segment in C#

[example]

My post Draw text on a line segment in C# shows how to draw text above or below a line segment, but that technique does not work very well if you want to center text over the segment. That post was really intended to let you draw characters one at a time over a sequence of small segments to draw curved text.

This example shows how to center text above or below a line segment. The technique is actually a lot easier than the technique used by the previous post.

[example]

The approach is to draw the text centered in a rectangle and then move that rectangle into position so it is centered above or below the line segment. The left side of the picture on the right shows how the text is drawn above the origin in the X-Y plane. The result is translated so the origin moves to the center of the line segment as shown on the right side of the picture.

The most interesting part of the example is the following DrawTextOverSegment method, which draws the text.

// Draw text centered above or below the line segment p1 --> p2.
private void DrawTextOverSegment(Graphics gr,
    Brush brush, Font font, string text,
    PointF p1, PointF p2,
    bool text_above_segment)
{
    // Save the Graphics object's state.
    GraphicsState state = gr.Save();

    // Get the segment's angle.
    float dx = p2.X - p1.X;
    float dy = p2.Y - p1.Y;
    float angle = (float)(180 * Math.Atan2(dy, dx) / Math.PI);

    // Find the center point.
    float cx = (p2.X + p1.X) / 2;
    float cy = (p2.Y + p1.Y) / 2;

    // Translate and rotate the origin
    // to the center of the segment.
    gr.RotateTransform(angle, MatrixOrder.Append);
    gr.TranslateTransform(cx, cy, MatrixOrder.Append);

    // Get the string's dimensions.
    SizeF size = gr.MeasureString(text, font);

    // Make a rectangle to contain the text.
    float y = 0;
    if (text_above_segment) y = -size.Height;
    RectangleF rect = new RectangleF(
        -size.Width / 2, y,
        size.Width, size.Height);

    // Draw the text centered in the rectangle.
    using (StringFormat sf = new StringFormat())
    {
        sf.Alignment = StringAlignment.Center;
        sf.LineAlignment = StringAlignment.Center;
        gr.DrawString(text, font, brush, rect, sf);
    }

    gr.Restore(state);
}

The method starts by calling the Graphics object’s Save method to save the object’s current state. We will later restore that stater to remove the rotation and transformation that the method adds to the Graphics object.

Next, the code calculates the X and Y differences between the segment’s two end points and uses them to calculate the segment’s angle. It also calculates the segment’s midpoint.

The method then adds a rotation to transform anything that it draws so it is rotated by the same amount as the line segment. It follows that with a translation transformation to move the origin to the segment’s center point.

Next, the method must figure out what rectangle to use when drawing the text. It starts by measuring the size of the text. It then checks its text_above_segment parameter to determine whether the text should be drawn above or below the line segment. Depending on the parameter’s value, the code defines a rectangle that lies above or below the origin and centered horizontally.

After that setup, the method simply draws the text centered inside the rectangle. The rotation and translation transformations rotate the text and move it so it is centered over or below the line segment.

The method finishes by calling the Graphics object’s Restore method so the calling code doesn’t need to worry about the transformations messing up future graphics.

Download the example to see other details.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Resize images in C#

[resize images]

You can use MS Paint to easily resize images. Unfortunately, it won’t preserve transparency if you save the result. I often need to resize transparent images so I finally got around to writing this example. It lets you resize images and save the result while preserving transparency.

This example is relatively simple. It lets you load and resize an image and then save the result. You specify the scale factor as a fraction. You cannot indicate the desired size of the new image. It does show you the new size, however, so you can just tweak the scale factor until you get the size you want. It also always sizes the image proportionally so you can’t distort the result.

The key to the program is the following ShowResult method, which resizes the image and displays the result.

// Make and show the resized image.
private void ShowResult()
{
    lblSize.Text = "Size:";
    picResult.Visible = false;
    if (OriginalImage == null) return;

    // Get the scale.
    float scale;
    if (!float.TryParse(txtScale.Text, out scale))
        return;
    if (scale < 0.0001) return;

    int width = (int)(OriginalImage.Width * scale);
    int height = (int)(OriginalImage.Height * scale);
    lblSize.Text = string.Format("Size: {0} x {1}",
        width, height);

    // Make the resized image.
    Bitmap bm = new Bitmap(width, height);
    using (Graphics gr = Graphics.FromImage(bm))
    {
        gr.InterpolationMode = InterpolationMode.HighQualityBicubic;
        Rectangle dest_rect = new Rectangle(0, 0,
            width, height);
        Rectangle source_rect = new Rectangle(0, 0,
            OriginalImage.Width, OriginalImage.Height);
        gr.DrawImage(OriginalImage,
            dest_rect, source_rect, GraphicsUnit.Pixel);
    }
    picResult.Image = bm;
    picResult.Visible = true;
}

The method first resets the lblSize label and sets the picResult PictureBox control’s image to null.

Next, the code tries to parse the text in the txtScale text box to see what scale factor you want to use. The method uses the scale factor to calculate the new image size and displays its dimensions in the lblSize label.

The method then makes a new Bitmap with the desired size and creates an associated Graphics object. It sets the Graphics object’s InterpolationMode property so the object will resize images smoothly.

The code creates rectangles representing the original and new bitmaps’ areas. I then uses the Graphics object’s DrawImage method to copy the original image onto the new Bitmap.

Finally, the code displays the resized image in the picResult PictureBox and makes that control visible.

The example program only contains a few other pieces of code, which help it load and save image files. Download the example to see those details.


Download Example   Follow me on Twitter   RSS feed   Donate




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

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




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

Adjust an image’s opacity in C#

[example]

I recently wanted to reduce an image’s contrast by adjusting its opacity. If you draw a semi-transparent image on top of a white background, the result looks washed out so it can act as a background for text or whatever.

Realizing that WPF allows you easily adjust an image’s opacity, I decided to write the program in WPF. Everything went fine until I tried to save the image with reduced opacity. I have code to do that, but it doesn’t work well with really large images and that’s what I had here. I had yet again fallen for WPF’s slogan, “Twice as flexible and only ten times as hard.” In this example, the Opacity property makes adjusting an image’s opacity easy, but WPF is terrible at bitmap manipulation so saving the result was impossible.

Anyway, I switched to a Windows Forms application and the whole thing was easy. Use File > Open to load an image file. Then use the scroll bar to adjust the opacity. When you like the result, use File > Save to save the result. The program only saves files in PNG format because other file formats do not preserve opacity.

The following sections describe the program’s key pieces.

  • Managing the Scroll Bar
  • Displaying the Sample Image
    • ShowImage
    • MakeCheckerboard
    • SetOpacity

Managing the Scroll Bar

Scroll bars only display integer values, but I wanted the opacity to be between 0.00 and 1.00 with values set to the nearest 1/100th. To use scroll bars with fractional values, multiply the minimum and maximum values that you want to allow by a power of ten that converts those values into integers. For this example, I multiplied 0.00 and 1.00 by 100 so the new bounds are between 0 and 100.

Now in the scroll bar’s Scroll event handler, divide the value by that same power, in this case 100. The following code shows the example’s Scroll event handler.

// Change the opacity.
private void scrOpacity_Scroll(object sender, ScrollEventArgs e)
{
    OpacityValue = (scrOpacity.Value / 100f);
    lblOpacity.Text = OpacityValue.ToString("0.00");
    ShowImage();
}

The code divides the scroll bar’s value by 100. It uses the floating point value 100f instead of the integer value 100 so the program doesn’t perform integer division and discard any digits beyond the decimal point.

The event handler displays the new opacity value in the lblOpacity label. It then calls the ShowImage method described shortly to display the adjusted image.

If you want to allow the user to select a particular largest value in the scroll bar, then you must set Maximum = [desired maximum] + LargeChange - 1. In this example, I want to allow the user to be able to select values between 0 and 100 (which are converted into 0.00 to 1.00). The scroll bar’s LargeChange property is 10, so I need to set its Maximum property to 100 + 10 – 1 = 109. Don’t ask me what Microsoft was thinking when they decided to do it this way.

Displaying the Sample Image

The example uses the following code to store the originally loaded picture and a version with adjusted opacity.

// The loaded image.
private Bitmap OriginalImage = null;

// The adjusted image.
private Bitmap AdjustedImage = null;

When you load a new picture, the program stores it in the OriginalImage field.

Whenever you adjust the scroll bar or open a new picture, the program adjusts the original image’s opacity and stores the result in the AdjustedImage field.

The ShowImage method creates the adjusted image and displays it. That method uses the MakeCheckerboard and SetOpacity methods. The following sections describe those three methods.

ShowImage

The following code shows the ShowImage method.

// Draw the loaded image with the selected opacity.
private void ShowImage()
{
    // Make sure we have an input picture.
    if (OriginalImage == null) return;

    // Create a sample image.
    // Make a background image.
    Bitmap sample_bm =
        MakeCheckerboard(
            OriginalImage.Width,
            OriginalImage.Height, 64);

    // Draw the adjusted omage over the checkerboard.
    AdjustedImage = SetOpacity(OriginalImage, OpacityValue);
    using (Graphics gr = Graphics.FromImage(sample_bm))
    {
        gr.DrawImage(AdjustedImage, 0, 0);
    }

    picSample.Image = sample_bm;
}

This method does nothing if you have not yet loaded an image into the OriginalImage field.

If an image is loaded, the method calls the MakeCheckerboard method to creates a blue and yellow checkerboard to fit the original image. It then calls the AdjustOpacity method to create a copy of the original image with its opacity adjusted. The program then draws the adjusted image on top of the checkerboard so you can see the blue and yellow squares showing through the new image.

MakeCheckerboard

The following code shows the MakeCheckerboard method.

// Make a checkerboard bitmap.
private Bitmap MakeCheckerboard(int width, int height, int size)
{
    int num_rows = height / size + 1;
    int num_cols = width / size + 1;
    Bitmap bm = new Bitmap(width, height);
    using (Graphics gr = Graphics.FromImage(bm))
    {
        for (int r = 0; r < num_rows; r++)
        {
            for (int c = 0; c < num_cols; c++)
            {
                Rectangle rect = new Rectangle(
                    c * size, r * size, size, size);
                if ((r + c) % 2 == 0)
                    gr.FillRectangle(Brushes.Blue, rect);
                else
                    gr.FillRectangle(Brushes.Yellow, rect);
            }
        }
    }
    return bm;
}

This method calculates the number of rows and columns it needs to fill the desired area with squares of the desired size. It then creates a Bitmap that has the indicated width and height.

Next, the code loops through the rows and columns. For each row/column combination, the method makes a Rectangle in the corresponding location in the bitmap. If the sum of the row plus the column is even, the method makes it blue. If the sum is odd, the method makes it yellow. That makes the squares alternate between blue and yellow.

After if finishes drawing the squares, the method returns the bitmap.

SetOpacity

Probably the most interesting and least obvious part of the program is the following SetOpacity method.

// Set the image's opacity.
private Bitmap SetOpacity(Bitmap input_bm, float opacity)
{
    // Make the new bitmap.
    Bitmap output_bm = new Bitmap(
        input_bm.Width, input_bm.Height);

    // Make an associated Grpahics object.
    using (Graphics gr = Graphics.FromImage(output_bm))
    {
        // Make a ColorMatrix with the opacity.
        ColorMatrix color_matrix = new ColorMatrix();
        color_matrix.Matrix33 = opacity;

        // Make the ImageAttributes object.
        ImageAttributes attributes = new ImageAttributes();
        attributes.SetColorMatrix(color_matrix,
            ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

        // Draw the input biitmap onto the Graphcis object.
        Rectangle rect = new Rectangle(0, 0,
            output_bm.Width, output_bm.Height);

        gr.DrawImage(input_bm, rect,
            0, 0, input_bm.Width, input_bm.Height,
            GraphicsUnit.Pixel, attributes);
    }
    return output_bm;
}

This method creates a new bitmap with the same size as the original one and makes an associated Graphics object.

Next, the code creates a ColorMatrix object. Basically the program will treat each pixel’s red, green, blue, and alpha components as a vector and multiply it by the ColorMatrix. Initially that object represents an identity matrix, so if you apply it to a bitmap, the bitmap is unchanged. The [3, 3] position in the matrix corresponds to the scale factor for the alpha component. The code sets that value to the opacity, so when the matrix is applied to the image, each pixel’s alpha component is multiplied by that value. If the image starts out opaque, then that sets the image’s opacity.

Having set the ColorMatrix object’s [3, 3] entry, the method creates a new ImageAttributes object and sets its color matrix to the lone that we just created.

Finally, the method calls the Graphics object’s DrawImage method to draw the original image onto the output bitmap while applying the matrix. The method then returns the result.

Conclusion

Download the example to see additional details including how the program loads and saves images. You may also want to experiment with the ColorMatrix object. For example, try leaving the [3, 3] entry equal to 1, and setting one or more of the [0, 0], [1, 1], or [2, 2] entries to 0.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Find hexes a certain distance from a target hex in C#

[hex]

My post Draw a hexagonal grid in C# shows how you can draw a grid made up of hexagons. This post shows how you can find the hexes that are a given distance N from a target hex.

Basic Idea

The basic idea is relatively straightforward. Start N hexes south of the target hex. Then move in the following directions.

  • N hexes northeast
  • N hexes north
  • N hexes northwest
  • N hexes southwest
  • N hexes south
  • N hexes southeast

At this point you will have visited all of the hexes that are distance N away from the target hex.

[hex]

Navigation Methods

The idea is straightforward. Figuring out exactly how you move from one hex to the next can be a bit confusing. In a square grid, you simply add or subtract one from the row or column to move from one square to the next. In a hex grid, you may need to add or subtract one to both the row and column, and which you do depends on whether the target hex’s column is odd or even. For example, to move southeast from hex (1, 1), you add one to both the row and column to get (2, 2). Then to move southeast from hex (2, 2), you add one only to the column to get (2, 3).

To make navigation eassier, this example uses the following methods to move row and column values to the next hex in the desired direction.

// Adjust the row and column to move
// to the northeast neighbor.
private void MoveNE(ref int row, ref int col)
{
    if (col % 2 == 0) row--;
    col++;
}

// Adjust the row and column to move
// to the southeast neighbor.
private void MoveSE(ref int row, ref int col)
{
    if (col % 2 == 1) row++;
    col++;
}

// Adjust the row and column to move
// to the northwest neighbor.
private void MoveNW(ref int row, ref int col)
{
    if (col % 2 == 0) row--;
    col--;
}

// Adjust the row and column to move
// to the southwest neighbor.
private void MoveSW(ref int row, ref int col)
{
    if (col % 2 == 1) row++;
    col--;
}

// Adjust the row and column to move
// to the north neighbor.
private void MoveN(ref int row, ref int col)
{
    row--;
}

// Adjust the row and column to move
// to the south neighbor.
private void MoveS(ref int row, ref int col)
{
    row++;
}

The methods that move in the northeast, northwest, southeast, and southwest directions check the starting column and then add or subtract one from the row if appropriate. They then update the column.You can study the picture earlier that showed the hex numbering system to verify that these methods work.

The methods that move north and south simply add or subtract one from the starting row.

The following FindAtRange method puts those methods together to find the hexes at a given distance from a target hex.

// Return a list of hexes that are a given
// distance from the target hex.
private List FindAtRange(int row, int col, int range)
{
    List neighbors = new List();

    // Start in the south.
    row += range;

    // Move northeast.
    for (int i = 0; i < range; i++)
    {
        MoveNE(ref row, ref col);
        neighbors.Add(new PointF(row, col));
    }

    // Move north.
    for (int i = 0; i < range; i++)
    {
        MoveN(ref row, ref col);
        neighbors.Add(new PointF(row, col));
    }

    // Move northwest.
    for (int i = 0; i < range; i++)
    {
        MoveNW(ref row, ref col);
        neighbors.Add(new PointF(row, col));
    }

    // Move southwest.
    for (int i = 0; i < range; i++)
    {
        MoveSW(ref row, ref col);
        neighbors.Add(new PointF(row, col));
    }

    // Move south.
    for (int i = 0; i < range; i++)
    {
        MoveS(ref row, ref col);
        neighbors.Add(new PointF(row, col));
    }

    // Move southeast.
    for (int i = 0; i < range; i++)
    {
        MoveSE(ref row, ref col);
        neighbors.Add(new PointF(row, col));
    }

    return neighbors;
}

This method creates a list of points to hold the hex names. It then sets variables row and col to represent the hex that is N spaces below the target hex.

The method then uses the movement methods to follow the instructions given earlier and adds the corresponding hexes to its result list. Then method then returns the list and is done.

Using FindAtRange

The following code shows how the main program’s MouseClick event handler displays the hexes at a given range from the hex clicked.

// Add the clicked hexagon to the Hexagons list.
private void picGrid_MouseClick(object sender, MouseEventArgs e)
{
    int row, col;
    PointToHex(e.X, e.Y, HexHeight, out row, out col);
    
    int range = int.Parse(txtRange.Text);
    Hexagons = FindAtRange(row, col, range);

    string txt = "";
    foreach (PointF point in Hexagons)
    {
        txt += "(" + point.X.ToString("0") +
            ", " + point.Y.ToString("0") + ") ";
    }
    txtCells.Text = txt;

    picGrid.Refresh();
}

This code calls the PointToHex method described in the previous post to see which hex you clicked. it then parses the range entered in the txtRange TextBox and passes the row, column, and range into the FindAtRange method. It saves the results in the Hexagon list so the Paint event handler can fill those hexes later. (Download the example to see how that works.)

The event handler finishes by building a string that lists the hexes that it found.

See the previous post and download the example to see additional details such as how the program determines which hex contains the point you clicked, how the Paint event handler draws the hexes, and how that event handler highlights the hexes at a given range from a target.


Download Example   Follow me on Twitter   RSS feed   Donate




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