Serialize and deserialize multiple images in C#


[serialize]

This example shows how you can serialize and deserialize multiple images together in a single serialization. It also talks a bit about database design. (For more information about database design including this issue, see my book, Beginning Database Design Solutions.)

Recently someone asked if you could store multiple images in a single record in a database. The answer to that kind of question is usually, “Yes, if you want to badly enough.” He eventually found some posts that did it by creating multiple fields in the data table and then storing an image in each.

The basic idea for storing an image in a database is to convert the image into bytes and then store the bytes in some sort of non-specific byte field. In some databases, that kind of field has the Binary Large Object (BLOB) data type. In an Access database, you can use a field with the OLE Object data type. For an example that does this, see the post Save images in an Access database in C#.

From there, it’s not too hard to create multiple BLOB or OLE Object fields and use them to store multiple images in each record. One of my rules of thumb for database design, however, is, “There’s no such thing as two.” The idea is that, if you are asked to add two of the same kind of field to a database table, then what’s to prevent your customer from later asking for three? Or four? Or more?

Adding one instance of something (like a headshot) in a table is fine. But if you then decide to add a second picture (perhaps a spouse picture) sends you down a slippery slope. If you include a spouse picture, then why not pictures of your children? Or pets? Or cars? You can certainly add a second picture field, but you’d better be absolutely certain that you won’t need to change the number of those fields later.

For example, suppose you use six fields, but then later hire an employee who has a spouse and seven children. Now you need to create a new database design, build the new database, copy your old data into it, and modify any programs that use it. (Or convince your new employee to put three children up for adoption.)

In the following sections, I’ll describe two ways that you can handle this issue, the “right” way and a somewhat more interesting way.

The “Right” Way

[serialize]

The officially sanctioned way to handle this issue using classic database design techniques is to create a second detail table and link it to the original table by using a key field. For example, a People table might include a PersonId field. The Pictures table would also include a PersonId field. To find the pictures associated with a particular person, you would search the Pictures table for records with the matching PersonId value. The picture on the right shows the relationship between those two tables.

Now you can add as many pictures as you like to any person’s record. This solution also has the distinct advantage that you can add other fields to the Pictures table. For example, the design shown in the diagram includes a Title field where you can indicate the type of picture such as Headshot, Spouse, Child, Pet, or Car. Later you could search the database to find a specific type of picture. For example, you could use the following SQL query to find employees and their headshots.

SELECT FirstName, LastName, Picture
  FROM People, Pictures
 WHERE People.PersonId = Pictures.PersonId
   AND Pictures.PictureType = 'Headshot'

An Interesting Alternative

An interesting alternative is to serialize a record’s images and place them in a single BLOB field. The technique of serializing objects is useful in other circumstances, too. For example, it lets you save and restore images (or other data) in files or transmit images across a network.

The example program uses a BinaryFormatter to serialize images into a MemoryStream. The program uses the following using directives to make using those classes easier.

using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

When it starts, the program uses the following code to serialize and deserialize three images.

private void Form1_Load(object sender, EventArgs e)
{
    // Add the files to a list.
    List<Image> input_images = new List<Image>();
    input_images.Add((Bitmap)picSource1.Image);
    input_images.Add((Bitmap)picSource2.Image);
    input_images.Add((Bitmap)picSource3.Image);

    // Serialize.
    byte[] bytes;
    using (MemoryStream ms = new MemoryStream())
    {
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(ms, input_images);
        bytes = ms.ToArray();
    }

    // Display the serialization bytes.
    txtHex.Text = BitConverter.ToString(
        bytes, 0).Replace('-', ' ');
    txtHex.Select(0, 0);

    // Deserialize.
    using (MemoryStream ms = new MemoryStream(bytes))
    {
        BinaryFormatter formattter = new BinaryFormatter();
        List<Image> output_images =
            (List<Image>)formattter.Deserialize(ms);
        picDest1.Image = output_images[0];
        picDest2.Image = output_images[1];
        picDest3.Image = output_images[2];
    }
}

This code first creates a List<Image> and adds the three images (stored in PictureBox controls) to it. It then creates a byte[] to hold the binary serialization.

Next, the code creates a MemoryStream. It makes a BinaryFormatter object and uses its Serialize method to serialize the list of images into the stream. It then calls the stream’s ToArray method to convert the bytes that it contains into a byte array. This is the array of bytes that you would store in the database.

The program then uses the BitConverter class to display a textual representation of the serialization, just so you can get an idea that the serialization actually exists. (You can also get an idea of its size.)

Next, the code deserializes the serialized data. To do that, it creates a new MemoryStream associated with the byte array. It creates a new BinaryFormatter and uses its Deserialize method to deserialize the bytes and recreate the list of images. Finally, it displays the images in a new set of PictureBox controls so you can see that they have been recreated correctly.

To Be Continued…

This example shows how to serialize and deserialize a list of images. You could then store the serialization in a database so each record in a table could include any number of images.

That technique has the advantage that it works. You can also use a similar technique to store just about any other kind of data in a database table or file. For example, you could store audio, video, a network, or a hierarchical data structure in database records.

You could even store pieces of data that you didn’t not anticipate in a BLOB field without rebuilding the database. For example, if you’re working with an existing database that already stores images in a BLOB and you now need to add audio data, you could serialize the pictures together with the audio data. You would need to write a program to re-serialize the data, but you would not need to change the database structure.

[Beginning Database Design Solutions]

HOWEVER, this technique has the huge disadvantage that it doesn’t let you search the data stored in the BLOB fields. For example, you could not search for headshot images. For that reason alone I would suggest that you use the “right” way unless the database is already built and you’re not allowed to change its design.

In my next post, I’ll explain how you can use similar techniques to serialize and deserialize a list of images in a file. To learn more about how this example works, download the example program. And to learn a lot more about database design, see my book Beginning Database Design Solutions.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in database, files, serialization | Tagged , , , , , , , , , , , , , | 1 Comment

Let the user drag pictures in a picture list in C#

[picture list]

The post Improve the picture list in C# showed how you can let the user add, remove ,and rearrange images in a picture list. To rearrange the list, the user right-clicks on a picture and selects Move Left or Move Right commands. That works, but can be slow if the user wants to move an image many positions in the picture list. It would be easier to delete and re-add the image. This example shows how you can allow the user to drag a picture to a new position in the list

The previous example stored the images in a list. Whenever the user rearranged the pictures, the program created PictureBox controls to display them. This example uses a list holding the PictureBox controls that contain the pictures. When the user rearranges the pictures, the program rearranges the PictureBox controls instead of creating new ones.

The program uses the following code to store the controls.

// The currently loaded PictureBoxes.
private List PictureBoxes =
    new List();

The code uses the following variables to keep track of the PictureBox that the user is dragging.

// Used to drag PictureBoxes.
private PictureBox DragPic = null;
private Point DragOffset;

The value DragPic is the PictureBox that the user is currently dragging. The DragOffset value gives the X and Y distances between the location of DragPic and the mouse’s position. You’ll see how that works when you look at the mouse handling code.

The following code executes when the user presses the mouse down over a PictureBox.

// Start dragging the control or display the context menu.
private void pic_MouseDown(object sender, MouseEventArgs e)
{
    PictureBox pic = sender as PictureBox;

    if (e.Button == MouseButtons.Left)
    {
        // Start dragging.
        DragPic = pic;
        int dx = -e.Location.X;
        int dy = -e.Location.Y;
        DragOffset = new Point(dx, dy);

        // Move the PictureBox to the top of the
        // panPictures stacking order.
        panPictures.Controls.SetChildIndex(pic, 0);

        // Let panPictures handle the MouseMove and MouseUp.
        DragPic.Capture = false;
        panPictures.Capture = true;
        panPictures.MouseMove += panPictures_MouseMove;
        panPictures.MouseUp += panPictures_MouseUp;
    }
    else
    {
        // Get the mouse's location in panPictures coordinates.
        Point screen_point = pic.PointToScreen(e.Location);
        Point parent_point = panPictures.PointToClient(screen_point);

        // Display the context menu.
        ShowContextMenu(new Point(
            parent_point.X,
            parent_point.Y));
    }
}

If the user has pressed the left mouse button, the code saves the DragPic and sets DragOffset to the negative of the mouse’s location. Because the PictureBox raised this event handler, the mouse’s position is with respect to that control’s origin so DragOffset indicates the X and Y distances between the mouse and the control’s upper left corner.

The code then calls the SetChildIndex method for the collection of PictureBox controls within the Panel control that forms the picture list. It uses that method to move the control under the mouse to the top of the stacking order so it appears above the other pictures in the picture list. (Dragging behind the other controls is weird.)

Next, the program sets dragPic.Capture to false to release the mouse capture that started when the user pressed the mouse down over the control. It sets panPictures.Capture to true to give the Panel control the mouse so it receives future mouse events.

Finally, the event handler installs MouseMove and MouseUp event handlers to capture future mouse events.

If the user pressed the right mouse button over the PictureBox, the code displays a context menu just as the previous example did.

The following code shows the MouseMove event handler.

// Move a PictureBox.
private void panPictures_MouseMove(object sender, MouseEventArgs e)
{
    int x = e.Location.X + DragOffset.X;
    int y = e.Location.Y + DragOffset.Y;
    DragPic.Location = new Point(x, y);
}

This code adds the DragOffset value to the mouse’s current position. Remember that the mouse is now captured by the Panel control that holds the PictureBox, so its position is with respect to that control. The DragOfffset value gives the distance between the mouse’s initial position and the PictureBox control’s upper left corner, so the result is where the PictureBox should be moved to keep the mouse over the same position on the control.

After it calculates the new position, the code simply moves the PictureBox.

The following code shows the new MouseUp event handler.

// Stop dragging DragPic.
private void panPictures_MouseUp(object sender, MouseEventArgs e)
{
    DragPic = null;
    panPictures.MouseMove -= panPictures_MouseMove;
    panPictures.MouseUp -= panPictures_MouseUp;
    OrderPictureBoxes();
}

This code sets DragPic to null to indicate that no drag is in progress. It uninstalls the new MouseMove and MouseUp event handlers, and then calls the following OrderPictureBoxes method.

// Rearrange the picture list so the controls
// are ordered by their X coordinates.
private void OrderPictureBoxes()
{
    // Sort the PictureBoxes list.
    PictureBoxes.Sort((pic1, pic2) =>
        pic1.Location.X.CompareTo(pic2.Location.X));

    // Rearrange the controls.
    ArrangePictureBoxes();
}

Recall that PictureBoxes is the program’s list of PictureBox controls. This method calls that list’s Sort method. It uses a lambda expression to indicate the function that should be used to sort the PictureBox controls in the list. This lambda expression takes two PictureBox controls as parameters and compares their X coordinates.

After the controls are sorted by their X coordinates, the code calls the following ArrangePictureBoxes method.

// Arrange the PictureBoxes.
private void ArrangePictureBoxes()
{
    int ymax = 0;
    int x = PictureMargin;
    int y = PictureMargin;
    foreach (PictureBox pic in PictureBoxes)
    {
        pic.Location = new Point(x, y);
        x += pic.Width + PictureMargin;
        if (ymax < pic.Height) ymax = pic.Height;
    }

    // Position one placeholder PictureBox.
    y = ymax + 2 * PictureMargin;
    Placeholder.Location = new Point(x, y);
}

This method is similar to the code used by the previous example to arrange its picture list. It sets variables x and y to the position where the first control should be placed. It then loops through the controls, positions each, and adds each control’s width plus a margin to the value x.

The method finishes by placing the Placeholder PictureBox to the right of the other controls so there is room for the user to right-click to the right of all of the pictures in the picture list. See the previous example for information about the placeholder control.

With the ability to drag images into new positions, the picture list is quite easy to use. Download the example to experiment with it. To see additional details, look at the code and see the previous example.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in controls, lists, user interface | Tagged , , , , , , , , | Leave a comment

Improve the picture list in C#

[picture list]

My post Make a picture list in C# explained how you can make a picture list that lets the user add, remove, and rearrange pictures. This post adds a small improvement.

The previous example displayed images in PictureBox controls with BorderStyle set to None. Unfortunately, if you set the BorderStyle to something else, then the context menu did not line up properly when you right-clicked on one of those controls. When you pressed and released the mouse, the context menu jumped slightly. This wasn’t a big deal, but it was annoying so I made that example use borderless PictureBox controls for its picture list.

The following code snippet shows how the previous example calculated the mouse’s position in panPictures coordinates.

// Display the context menu.
PictureBox pic = sender as PictureBox;
ShowContextMenu(new Point(pic.Left + e.X, pic.Top + e.Y));

This code adds the PictureBox control’s upper left corner coordinates to the mouse’s coordinates in that control’s coordinate system. In other words, it starts at the control’s upper left corner and then adds the coordinates of the mouse within the control to get the control’s position relative to the panPictures parent control.

The problem is that the coordinates reported by the PictureBox are relative to that control’s client area, and that area does not include the control’s border. That means the mouse’s location is off by the thickness of the control’s left and top borders.

There doesn’t seem to be a way to find the thickness of a control’s border directly, so you can’t just add it into the calculation. Fortunately, you can convert a point from the coordinate system of one control to that of another. The new example does that in the following snippet.

PictureBox pic = sender as PictureBox;

// Get the mouse's location in panPictures coordinates.
Point screen_point = pic.PointToScreen(e.Location);
Point parent_point = panPictures.PointToClient(screen_point);

// Display the context menu.
ShowContextMenu(new Point(
    parent_point.X,
    parent_point.Y));

This code calls the PictureBox control’s PointToScreen method to convert the mouse’s location to screen coordinates. That gives the mouse’s position relative to the screen’s upper left corner. The code then calls the panPictures control’s PointToClient method to convert the screen coordinates into the panPictures control’s coordinate system.

This is actually a more direct way to calculate the mouse’s position. It also works no matter what border style you give to the PictureBox controls used by the picture list.

In my next post, I’ll add another enhancement that will force a redesign of much of the program.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in controls, lists, user interface | Tagged , , , , , , , , | 1 Comment

Make a picture list in C#

[picture list]

This example shows how to build a picture list that lets the user add, remove, and rearrange pictures at run time. If you right-click between two pictures or to the left or right of all of the pictures, the program displays a context menu with the single command Insert Picture. If you select that command, the program displays an OpenFileDialog to let you select a new picture to add at that position.

If you right-click on, above, or below a picture, the context menu contains the commands Move Left, Move Right, and Delete Picture. The Move Left command is disabled for the leftmost picture. Similarly the Move Right command is disabled for the rightmost picture. The Delete Picture command asks you to confirm the deletion before it removes the picture that you clicked.

The following sections explain how the program works.

Arranging Pictures

At design time, I created a Panel control named panPictures. That control’s AutoScroll property is true, so the control displays scroll bars if it is not big enough for all of its contents to be visible.

The program uses the following form-level variables to keep track of the pictures being displayed.

// The currently loaded pictures.
private List<Bitmap> Pictures = new List();
private const int PictureMargin = 8;

// The index of the picture we clicked or
// the picture before which we clicked.
private int ClickedIndex = -1;

The Pictures list holds the pictures in left-to-right order. The value PictureMargin indicates how much space should be left between the pictures displayed in the panPictures control.

The code uses the ClickedIndex value later when it deals with mouse events.

Whenever the program changes its picture list, it calls the following method to display the pictures.

private void ArrangePanel()
{
    panPictures.Controls.Clear();
    int x = PictureMargin;
    int y = PictureMargin;
    foreach (Bitmap picture in Pictures)
    {
        PictureBox pic = new PictureBox();
        pic.SizeMode = PictureBoxSizeMode.AutoSize;
        pic.Location = new Point(x, y);
        pic.Image = picture;
        pic.Visible = true;
        pic.MouseDown += pic_MouseDown;
        panPictures.Controls.Add(pic);

        x += pic.Width + PictureMargin;
    }

    // Add one placeholder PictureBox.
    PictureBox placeholder = new PictureBox();
    placeholder.Location = new Point(x, y);
    placeholder.Size = new Size(0, 0);
    placeholder.Visible = true;
    placeholder.MouseDown += pic_MouseDown;
    panPictures.Controls.Add(placeholder);
}

This method first removes the children from the panPictures control. It then sets variables x and y to the spot where the first picture’s upper left corner should be.

Next, the code loops through the pictures in the Pictures list. For each picture, it creates a PictureBox control, positions it at (x, y), and makes it display the picture. It registers the control to receive MouseDown events and adds it to the panPictures control. The loop finishes by moving the value x so it leaves distance PictureMargin between this picture and the next one.

If the method stopped at this point, then the Panel control would provide scroll bars if necessary so you could scroll to the right edge of the last picture. Unfortunately that would not provide any space to the right of the final picture where you could right-click to open the context menu and use the Insert Picture command.

To work around that problem, the method adds one more PictureBox to the Panel control. This control has no size but is positioned PictureMargin pixels to the right of the final picture so you have an area where you can right-click.

Displaying the Context Menu

When you press the mouse down on one of the PictureBox controls, the following event handler executes.

private void pic_MouseDown(object sender, MouseEventArgs e)
{
    // Ignore left mouse clicks.
    if (e.Button != MouseButtons.Right) return;

    // Display the context menu.
    PictureBox pic = sender as PictureBox;
    ShowContextMenu(new Point(pic.Left + e.X, pic.Top + e.Y));
}

If you pressed the left mouse button, the code simply returns. If you pressed the right mouse button, the code gets the PictureBox under the mouse. Next, it adds the PictureBox control’s Left and Top values to the mouse’s position within the PictureBox to get the mouse’s position within the panPictures control. It then passes those coordinates to the ShowContextMenu method described shortly.

When you press the mouse down on the panPictures control, the following event handler executes.

private void panPictures_MouseDown(object sender, MouseEventArgs e)
{
    // Ignore left mouse clicks.
    if (e.Button != MouseButtons.Right) return;

    // Display the context menu.
    ShowContextMenu(e.Location);
}

This event handler also ignores left mouse button presses. If you pressed the right mouse button, the code passes the mouse’s coordinates to the ShowContextMenu method.

The following code shows the ShowContextMenu, which displays the picture list.

// Prepare the context menu and display it.
private void ShowContextMenu(Point location)
{
    // Assume we click after the final picture.
    bool clicked_on_picture = false;
    ClickedIndex = Pictures.Count;

    // See if we clicked on or before a picture.
    int x = location.X + panPictures.HorizontalScroll.Value;
    for (int i = 0; i < Pictures.Count; i++)
    {
        // See if we are before the next picture.
        x -= PictureMargin;
        if (x < 0)
        {
            ClickedIndex = i;
            break;
        }   

        // See if we are on this picture.
        x -= panPictures.Controls[i].Width;
        if (x < 0)
        {
            ClickedIndex = i;
            clicked_on_picture = true;
            break;
        }
    }

    // Enable and disable contect menu items.
    mnuMoveLeft.Enabled =
        (clicked_on_picture && (ClickedIndex > 0));
    mnuMoveRight.Enabled =
        (clicked_on_picture && (ClickedIndex < Pictures.Count - 1));
    mnuDeletePicture.Enabled = clicked_on_picture;
    mnuInsertPicture.Enabled = !clicked_on_picture;

    // Display the context menu.
    ctxPictures.Show(panPictures, location);
}

This method determines whether you pressed the mouse down on (or above or below) a picture, or whether you pressed it between two pictures (or to the left and right of all of the pictures). To do that, the code assumes that you did not click on a picture. It sets ClickedIndex to the index one beyond the last index in the Pictures list.

Next, the code sets variables x equal to the mouse location within the Panel control. It adds the control’s horizontal scroll value in case you have scrolled that control. For example, if you have scrolled the Panel by 100 pixels, then the pictures that it contains have been moved 100 pixels to the left. That means, in the coordinate system that contains the pictures, the mouse’s X position is 100 greater than the value given by the location parameter.

Having initialized x, the method loops through the pictures. For each picture, the code subtracts the value PictureMargin. If that makes x become less than zero, then the mouse lies just before the current picture. In that case, the code sets ClickedIndex equal to the current picture’s index and breaks out of the loop.

If x is still positive, the code subtracts the width of the current picture’s PictureBox control. If x is now negative, then the mouse lies above the current picture, so the code sets ClickedIndex to the current picture’s index. In that case it also sets clicked_on_picture to true so we remember that the mouse was over a picture.

After the loop ends, the code enables and disables the context menu’s commands appropriately. For example, it enables the Move Left command if the mouse is over a picture and it is not the leftmost picture.

The ShowContextMenu method finishes by displaying the context menu named ctxPictures.

Context Menu Commands

The following code executes when you select the Move Left command.

private void mnuMoveLeft_Click(object sender, EventArgs e)
{
    Bitmap bm = Pictures[ClickedIndex];
    Pictures.RemoveAt(ClickedIndex);
    Pictures.Insert(ClickedIndex - 1, bm);
    ArrangePanel();
}

This code sets variable bm equal to the picture that was clicked. It then removes that picture from the Pictures list and reinserts it one position to the left of its original position. The code finishes by calling ArrangedPanel to redisplay the picture list.

The following code executes when you select the Move Right command.

private void mnuMoveRight_Click(object sender, EventArgs e)
{
    Bitmap bm = Pictures[ClickedIndex];
    Pictures.RemoveAt(ClickedIndex);
    Pictures.Insert(ClickedIndex + 1, bm);
    ArrangePanel();
}

This code is similar to the Move Left command except it reinserts the clicked picture one position to the right of its original position.

The following code executes when you select the Delete Picture command.

private void mnuDeletePicture_Click(object sender, EventArgs e)
{
    if (MessageBox.Show(
        "Are you sure you want to delete this picture?",
        "Delete Picture?", MessageBoxButtons.YesNo) == DialogResult.Yes)
    {
        Pictures.RemoveAt(ClickedIndex);
        ArrangePanel();
    }
}

This code asks if you really want to delete the picture that you right-clicked. If you click Yes, the code simply removes the picture from the Pictures list and calls ArrangedPanel to redisplay the picture list.

Finally, the following code executes when you select the Insert Picture command.

// Let the user insert a picture.
private void mnuInsertPicture_Click(object sender, EventArgs e)
{
    try
    {
        if (ofdPicture.ShowDialog() == DialogResult.OK)
        {
            int i = 0;
            foreach (string filename in ofdPicture.FileNames)
            {
                Bitmap bm = new Bitmap(filename);
                Pictures.Insert(ClickedIndex + i, bm);
                i++;
            }
            ArrangePanel();
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

This code displays an OpenFileDialog. If you select one or more files and click Open, the code loops through the files that you selected. It loads each file into a Bitmap and adds the file to the next position in the Pictures list. Increasing the value i each time places the pictures in the Pictures list in the order in which they are returned by the OpenFileDialog. (The dialog returns the files in the order in which they were listed in the dialog, not in the order in which you selected them.)

Again, the method finishes by calling ArrangePanel to show the new arrangement of pictures in the picture list.

Conclusion

This method lets a program display a picture list that the user can manage reasonably intuitively. You could add other variations. For example, you could arrange the pictures vertically in a column or wrap the pictures in rows and columns.

You could also convert the whole picture list into a PictureList UserControl. (In fact, I even did that before I decided few people would be interested in that. If you do want to see the picture list converted into a control, post a comment below. If enough people want it, I’ll post it.

Meanwhile, download the example and give it a try. I think you’ll find the picture list very easy to use.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in controls, lists, user interface | Tagged , , , , , , , , | 1 Comment

Draw text with colors reversed along a sine wave in C#

[example]

The post Draw text with colors reversed along a diagonal line in C# demonstrated a general technique for splitting text (or any other image) into pieces that are drawn differently. That example showed how to draw text with some areas drawn with inverted colors.

The general approach is to draw text in different color schemes on different bitmaps. Then use those bitmaps to make TextureBrushes and use the brushes to fill different parts of the final image.

This example uses the following method to draw text above and below a sine curve differently.

// Draw sine split text centered in the indicated rectangle.
private void DrawSineSplitText(Graphics gr,
    string text, Font font, Rectangle rect,
    Brush top_bg_brush, Brush top_fg_brush,
    Brush bottom_bg_brush, Brush bottom_fg_brush,
    float num_waves, float y_scale)
{
    // Make bitmaps holding the text in different colors.
    Bitmap bm_top = new Bitmap(rect.Width, rect.Height);
    Bitmap bm_bottom = new Bitmap(rect.Width, rect.Height);

    // Make a StringFormat to center text.
    using (StringFormat sf = new StringFormat())
    {
        sf.Alignment = StringAlignment.Center;
        sf.LineAlignment = StringAlignment.Center;

        using (Graphics gr_top = Graphics.FromImage(bm_top))
        {
            gr_top.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
            gr_top.FillRectangle(top_bg_brush, rect);
            gr_top.DrawString(text, font, top_fg_brush, rect, sf);
        }

        using (Graphics gr_bottom = Graphics.FromImage(bm_bottom))
        {
            gr_bottom.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
            gr_bottom.FillRectangle(bottom_bg_brush, rect);
            gr_bottom.DrawString(text, font, bottom_fg_brush, rect, sf);
        }
    }

    // Fill the rectangle with the top bitmap.
    using (TextureBrush brush = new TextureBrush(bm_top))
    {
        gr.FillRectangle(brush, rect);
    }

    // Make a polygon to fill the bottom half.
    List<PointF> points = new List<PointF>();
    float mag = (font.Size * 96f / 72f) / 2 * y_scale;
    float y_offset = rect.Height / 2f;
    points.Add(new PointF(0, rect.Height));
    float x_scale = (float)(num_waves * 2 * Math.PI / rect.Width);
    for (int x = 0; x < rect.Width; x++)
    {
        float y = (float)(y_offset + mag * Math.Sin(x * x_scale));
        points.Add(new PointF(x, y));            
    }
    points.Add(new PointF(rect.Width - 1, rect.Height));

    // Fill the polygon.
    using (TextureBrush brush = new TextureBrush(bm_bottom))
    {
        gr.SmoothingMode = SmoothingMode.AntiAlias;
        gr.FillPolygon(brush, points.ToArray());
    }

    bm_top.Dispose();
    bm_bottom.Dispose();
}

The method starts by making the two bitmaps. It creates a StringFormat object to center text and then draws text using the desired foreground and background colors onto the bitmaps.

Next, the method makes a TextBrush out of the first bitmap and uses it to fill the drawing area.

The method then creates a List<PointF> and adds a point at the lower left corner of the image. It loops through X values for the result image’s pixels. For each X coordinate, the code uses the sine function to calculates a Y value. It scales the X values passed into the sine function to give the waves a nice width. It also scales the Y result so the curve passes through the middle part of the text. After it calculates the Y coordinate, the code adds the point to the points list.

After it has finished generating the points, the code adds a point at the lower right corner of the image. Together the points define a polygon that encloses the space below the sine curve on the image. The method uses the second bitmap to create a TextureBrush and then uses it to fill the polygon.

See the previous post and download this example to see other details. You can use similar techniques to color different parts of other images in various ways. The only trick is creating the polygons, rectangles, ellipses, or other shapes that define the pieces of the image that should be colored differently.


Download Example   Follow me on Twitter   RSS feed   Donate




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

Draw text with colors reversed along a diagonal line in C#

[example]

The post Draw text with colors reversed in its upper and lower halves in C# showed how you can draw text that has upper and lower halves with colors switched. This example uses a similar technique to switch the colors along a diagonal line.

The approach is similar to the one used by the previous example. The program creates two bitmaps that hold the text drawn with the different color combinations. The following code snippet draws the upper right and lower left parts of the drawing area.

// Fill the entire rectangle with the top version.
using (TextureBrush brush = new TextureBrush(bm_top))
{
    gr.FillRectangle(brush, rect);
}

// Fill the lower left corner with the bottom version.
Point[] points = 
{
    new Point(rect.X, rect.Y),
    new Point(rect.X, rect.Bottom),
    new Point(rect.Right, rect.Bottom),
};
using (TextureBrush brush = new TextureBrush(bm_bottom))
{
    gr.FillPolygon(brush, points);
}

Instead of filling two triangular pieces, this code first fills the entire drawing area with the colors that should be in the upper right corner. Filling the upper right corner specifically wouldn’t be too hard, but this is slightly easier.

Next, the code creates an array of points to define the lower left triangular area and uses the Graphics object’s FillPolygon method to fill that area with the lower-left colors.

That’s all there is to it. In my next post, I’ll show how to make one more variation that divides the text into pieces on either side of a sine curve.

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


Download Example   Follow me on Twitter   RSS feed   Donate




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

Draw text with colors reversed in its upper and lower halves in C#

[draw text]

This example and the two that follow show how to draw text with an interesting visual effect. The idea is quite simple. Make two bitmaps showing the text with its different color schemes. Then use those images to fill different parts of the final output image.

The program uses the following DrawSplitText method to draw its text.

// Draw split text centered in the indicated rectangle.
private void DrawSplitText(Graphics gr,
    string text, Font font, Rectangle rect,
    Brush top_fg_brush, Brush bottom_fg_brush)
{
    // Make bitmaps holding the text in different colors.
    Bitmap bm_top = new Bitmap(rect.Width, rect.Height);
    Bitmap bm_bottom = new Bitmap(rect.Width, rect.Height);

    // Make a StringFormat to center text.
    using (StringFormat sf = new StringFormat())
    {
        sf.Alignment = StringAlignment.Center;
        sf.LineAlignment = StringAlignment.Center;

        using (Graphics gr_top = Graphics.FromImage(bm_top))
        {
            gr_top.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
            gr_top.FillRectangle(bottom_fg_brush, rect);
            gr_top.DrawString(text, font, top_fg_brush, rect, sf);
        }

        using (Graphics gr_bottom = Graphics.FromImage(bm_bottom))
        {
            gr_bottom.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
            gr_bottom.FillRectangle(top_fg_brush, rect);
            gr_bottom.DrawString(text, font, bottom_fg_brush, rect, sf);
        }
    }

    // Fill the top and bottom halves of the rectangle.
    RectangleF top_rect = new RectangleF(
        rect.X, rect.Y, rect.Width, rect.Height / 2f);
    using (TextureBrush brush = new TextureBrush(bm_top))
    {
        gr.FillRectangle(brush, top_rect);
    }

    RectangleF bottom_rect = new RectangleF(
        rect.X, top_rect.Bottom, rect.Width, rect.Height / 2f);
    using (TextureBrush brush = new TextureBrush(bm_bottom))
    {
        gr.FillRectangle(brush, bottom_rect);
    }

    bm_top.Dispose();
    bm_bottom.Dispose();
}

The method first makes two bitmaps (named bm_top and bm_bottom) that have the same size as the area where it should draw the text. It then makes a StringFormat object and sets its alignment properties so it centers text vertically and horizontally.

Next, the code creates a Graphics object for the upper bitmap bm_top. It sets the object’s TextRenderingHint property to produce smooth text, fills the bitmap with the bottom bitmap’s foreground brush bottom_fg_brush, and then draws the text on it using the top foreground brush top_fg_brush. The result looks like this:


[draw text]

The program repeats those steps to draw the bottom bitmap bm_bottom so it looks like this:


[draw text]

Now the program makes a rectangle top_rect that fills the upper half of the drawing area. It makes a TextureBrush that uses top_bm as its texture and uses it to fill the upper rectangle in original Graphics object. Here’s the result at this point:


[draw text]

The method them repeats roughly the same steps to fill the bottom half of the drawing area with a TextureBrush that uses the bottom bitmap. That produces the final result shown at the top of this post.

The method finishes by disposing of the two bitmaps bm_top and bm_bottom. You could create those inside a using block to make the program dispose of them automatically, but that would make the level of indentation inconveniently deep. Either approach works as long as you remember to call the bitmaps’ Dispose methods.

In my next two posts, I’ll show how to draw text that is split in ways other than horizontally. Before you read them, you might want to download this example and see if you can split the text in new and interesting ways.


Download Example   Follow me on Twitter   RSS feed   Donate




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

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