Flow words around obstacles for document layout in C#


[document layout]

The example Flow blocks around obstacles for document layout in C# implements a document layout algorithm that flows blocks around obstacles. Moving from there to flowing words around obstacles is relatively simple. The following sections describe the two main places where I modified the previous algorithm to provide document layout for words instead of blocks.

The TextBlock Class

The previous example uses the Block class to represent a rectangle that should be flowed around obstacles. To flow words, this example derives a new TextBlock class from the Block class. The following code shows the new class.

public class TextBlock : Block
{
    public Font Font;
    public String Text;

    public TextBlock(string text, Font font, Graphics gr)
    {
        Font = font;
        Text = text;

        SizeF size = gr.MeasureString(Text, font);
        Bounds = new RectangleF(new PointF(), size);

        FontInfo font_info = new FontInfo(gr, font);
        TopHeight = font_info.AscentPixels;
    }

    // Draw the text block.
    public override void Draw(Graphics gr)
    {
        if (Bounds.X < 0) return;

        // Draw the box (optional). 
        base.Draw(gr);

        // Draw the text.
        gr.DrawString(Text, Font, Brushes.Black, Bounds);
    }
}

This class inherits from the Block class. It provides two new fields: Font and Text. You can probably guess that those are the font that the object should use to draw its text and the text that it should draw.

The constructor takes as parameters the block’s text, its font, and a Graphics object that it can use to measure the text. The code saves the font and text, and then calls the Graphics object’s MeasureString method to measure the text as drawn with the given font. It uses the text’s size to define the block’s bounds.

Next, the program must figure out where the text’s baseline should be. Recall that the previous example’s document layout method aligned blocks on their baselines. For the blocks, that was unnecessary. For text, that’s important so different words line up properly.

The distance between the top of a piece of text and its baseline is called its ascent. Unfortunately, neither the Font not Graphics objects give you the ascent directly. Fortunately, you can figure it out. For an explanation of how to do that and to see a picture showing how the ascent fits on the text, see my earlier post Get font metrics in C#. That method defines a FontInfo class that gives the font’s ascent. The program simply creates a FontInfo object for the font and saves its AscentPixels property in the TextBlock object’s TopHeight field.

The final piece of the TextBlock class is its Draw method, which overrides the same method in the Block class. The new method first calls the Draw class’s version of the method to draw a green box with a red baseline where the text will be positioned as shown in the picture at the top of the post. In the final program, you should comment out that call so the program only draws the text as in the following picture.


[document layout]

The method then draws the object’s text inside its bounds. Because the constructor defined the bounds so they would fit the text, the text fills the bounds completely. As a side effect, the text is centered both vertically and horizontally within the bounds.

I also made two minor changes to the Block class to make the derived TextBlock class work. First, I have the Block class a new parameterless constructor so the derived class didn’t need to pass the base constructor a bounds rectangle that it would not use.

Second, I changed the Draw method so it is virtual (so the TextBlock class can override it) and so it takes the parameters needed by the TextBlock class.

The Main Program

Instead of creating random blocks, the new example creates words that use random fonts. The following code shows how the program initializes its word data.

// Blocks to flow around obstacles.
private List<Block> Blocks;

// The text to draw.
private string Words = "Lorem ipsum dolor sit amet, ...";

// Define some blocks.
private void Form1_Load(object sender, EventArgs e)
{
    Obstacles = new List();
    Obstacles.Add(new RectangleF(0, 0, 64, 64));
    Obstacles.Add(new RectangleF(200, 50, 50, 100));
    Obstacles.Add(new RectangleF(50, 150, 100, 70));
    Obstacles.Add(new RectangleF(300, 200, 100, 64));

    Blocks = new List<Block>();
    Graphics gr = CreateGraphics();
    foreach (string word in Words.Split(' '))
        Blocks.Add(new TextBlock(word, RandomFont(), gr));
}

Notice that the variable Blocks is still a list of Block objects. The TextBlock class is derived from Block, so a TextBlock is a kind of block. That means the program can place TextBlock objects inside a List<Block>.

The string Words contains a long set of Lorem Ipsum words. For more information on this interesting test string, see the sire Lorem Ipsum.

The form’s Load event handler creates obstacles as before. It then breaks the Words string into words and uses each to create a TextBlock. The only new thing here is that the program calls the RandomFont method to generate each word’s font.

The RandomFont method simply picks a random font name, size, and style (bold, italic, regular, strikeout, or underline). Download the example to see the details.

Conclusion

The rest of the example is about the same as before. The Paint event handler calls the FlowBlocks method to arrange the blocks as before. The blocks are actually TextBlock objects instead of Block objects, but FlowBlocks doesn’t care.

The result is almost what we need to perform document layout with drop caps. The result shown here looks a bit strange because the obstacles are scattered around the drawing area rather than all places on the left edge. The words are also relatively large, so there are some fairly big gaps between words.

As usual, feel free to download the example and experiment with it. In my final post in this series, I’ll show how to handle paragraphs and pull the initial drop caps off of the first word in each paragraph. The result should look a lot more appealing than the one shown here.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, fonts, graphics, strings | Tagged , , , , , , , , , , , , | Leave a comment

Flow blocks around obstacles for document layout in C#


[document layout]

The following two document layout examples draw “illuminated” text by using an initial or drop cap.

Martin asked how we could do something similar when the text that follows the initial is drawn with different fonts. This is the first part of a three-part document layout example that does just that. This first post shows how you can perform layout for boxes, the second post will use these techniques to solve Martin’s problem, and the third will refine the technique to work more easily with drop caps.

This is a fairly long example, so I’ll describe it in pieces. Fortunately each of the methods is reasonably understandable if you focus only on its specific task and you don’t think about how the other pieces work until you get to them.

The Idea

Suppose we need to arrange some boxes inside a drawing area. The area contains some rectangular obstacles where we are not allowed to place our boxes.

The basic idea is to start in the upper left corner and arrange boxes in a row left to right. If we come to an obstacle, we skip over it. As we add boxes to the current row, we arrange the boxes so they all share a common baseline, which is somewhere within the boxes.

See the picture at the top of this post to see the idea. Note that all of the green boxes on the top row are aligned on their red baselines.

The first row skips past the first pink obstacle in the upper left corner. Block 0 is relatively large, so it is the only one that fits before the second obstacle interrupts the row. Blocks 1 through 4 fit on the first row, and then the document layout moves to a new row.

Only block 5, which is relatively small, fits on the second row before another obstacle gets in the way. The program places block 6, skips another obstacle, and then positions blocks 7 and 8 before starting the third row.

You can look at the remaining boxes and obstacles to get an idea about how the method works. You can probably devise other document layout methods, but this one seems to produce a reasonable result.

Also note that this would be a truly bizarre document layout for drop caps. Normally all of the drop caps would be at the left edge and the remaining text would flow to the right. This method handles that normal arrangement. It can also deal with one or two pictures scattered across the page.

The program uses the RectangleF structure to represent obstacles. It uses a Block class to represent the blocks that it should flow. The following section describes that class.

The following sections explain how the program arranges its blocks.

Obstacles and Blocks

The program uses the following Block class to represent the blocks that it should flow around obstacles.

public class Block
{
    public RectangleF Bounds;
    public float TopHeight;
    public float BottomHeight
    {
        get { return Bounds.Height - TopHeight; }
    }

    public Block(RectangleF bounds, float top_height)
    {
        Bounds = bounds;
        TopHeight = top_height;
    }

    // Draw the block.
    public void Draw(Graphics gr, int index, Font font)
    {
        if (Bounds.X < 0) return;

        gr.FillRectangle(Brushes.LightGreen, Bounds);
        gr.DrawRectangle(Pens.Green, Bounds);

        float x1 = Bounds.Left;
        float x2 = Bounds.Right;
        float y = Bounds.Top + TopHeight;
        gr.DrawLine(Pens.Red, x1, y, x2, y);

        gr.DrawString(index.ToString(),
            font, Brushes.Black,
            Bounds);
    }
}

This class defines two fields: Bounds and TopHeight. The Bounds value gives the block’s size and location. Initially only the size matters. [Insert your joke here.] After the block has been positioned, Bounds indicates where it should be drawn.

The TopHeight value is the distance from the block’s top to its baseline. The read-only BottomHeight property gives the distance from the block’s baseline to its bottom. That value is simply the block’s height minus its TopHeight value.

The class defines one simple initializing constructor.

The last part of the class defines a Draw method that draws the block on a Graphics object. That method fills the block’s area with light green, outlines it in green, draws a red line at the block’s baseline, and then draws the block’s index number inside the block’s area. (Look at the picture at the top of the post to see the result.)

The next section provides an overview of the methods that arrange the blocks. The sections after that describe each of those methods in greater detail.

Code Overview

The program uses the following methods to arrange blocks.

FlowBlocks
This is the high-level method that coordinates the document layout. It enters a loop that executes as long as any blocks are not positioned. Inside the loop, it calls PositionOneRow to position blocks on the next row. If no blocks fit on that row, it calls MoveDown to move down where a row might be drawn so it can hopefully position some blocks.
PositionOneRow
This method tries to position blocks in a row within a bounding area starting with a given Y coordinate. To do that, it tries to position one block, then two blocks, then three blocks, and so on until it finds a number of blocks that will not fit. For example, if it can position four blocks but not five blocks, then the method returns 4 and positions four blocks. To determine whether a given set of blocks fits on the row, the method calls BlocksFit.
BlocksFit
This method attempts to flow a given set of blocks around obstacles in a row with the given Y coordinate. If the blocks fit, the method leaves them positioned and returns true.
MoveDown
This method moves a Y coordinate downward to try to find a position for a new row. It is only called if the BlocksFit method was unable to fit even a single block on the next row. To make at least one block fit, the method moves the Y coordinate down so the next block will not overlap vertically with at least one obstacle. Note that the block may still overlap with other obstacles. In that case, the program will call MoveDown again until the block will fit or the document layout runs out of space.

The following sections show the methods’ code and describe them in greater detail. You may want to refer back to the overview as you read through the code.

FlowBlocks

The following code shows the top-level FlowBlocks method.

// Flow blocks around obstacles.
private void FlowBlocks(RectangleF bounds,
    List<RectangleF> obstacles, List<Block> blocks)
{
    float y = bounds.Y;

    // Repeat until we place all blocks or run out of room.
    int first_block = 0;
    while (first_block < blocks.Count)
    {
        // Position a row of blocks.
        int num_positioned = PositionOneRow(
            bounds, obstacles, blocks,
            ref first_block, ref y);

        // See if any fit.
        if (num_positioned == 0)
        {
            // None fit. Move down.
            MoveDown(bounds, obstacles, blocks[first_block], ref y);
        }

        // See if we have run out of room.
        if (y > bounds.Bottom)
        {
            // Position the remaining blocks at (-1, -1).
            for (int i = first_block; i < blocks.Count; i++)
            {
                blocks[i].Bounds.X = -1;
                blocks[i].Bounds.Y = -1;
            }
            break;
        }
    }
}

This method first sets variable y equal to the Y coordinate of the top of the drawing area. It uses variable first_block to keep track of the first block that has not yet been positioned. It sets that value to 0 and then enters a loop that continues until all of the blocks have been positioned.

Inside the loop, the method calls PositionOneRow to position blocks at the current Y coordinate. If no blocks will fit at that position, the method calls MoveDown to move the Y coordinate down so hopefully some blocks will fit.

After positioning some blocks or moving down, the method checks y to see if it has dropped off of the bottom of the document layout area. If we have run out of space, the method places the remaining unpositioned blocks at (-1, -1) and exits.

PositionOneRow

The following code shows the PositionOneRow method.

// Return the maximum number of blocks that will fit
// on one row starting at the indicated Y coordinate.
// If we position any blocks, update first_block and y.
private int PositionOneRow(RectangleF bounds,
    List<RectangleF> obstacles, List<Block> blocks,
    ref int first_block, ref float y)
{
    // Loop through the blocks.
    int last_that_fits = blocks.Count - 1;
    for (int i = first_block; i < blocks.Count; i++)
    {
        // See if we can place blocks[first_block]
        // through blocks[i].
        if (!BlocksFit(bounds, obstacles, blocks, first_block, i, y))
        {
            last_that_fits = i - 1;
            break;
        }
    }

    // If no blocks fit, return 0.
    if (last_that_fits < first_block) return 0;

    // Position the blocks that fit again.
    BlocksFit(bounds, obstacles, blocks,
        first_block, last_that_fits, y);

    // Find the maximum y coordinate for these blocks.
    for (int i = first_block; i <= last_that_fits; i++)
    {
        if (y < blocks[i].Bounds.Bottom)
            y = blocks[i].Bounds.Bottom;
    }

    // Update first_block.
    int num_blocks_positioned = last_that_fits - first_block + 1;
    first_block = last_that_fits + 1;

    // Return the number of blocks that fit.
    return num_blocks_positioned;
}

The parameter first_block gives the index of the first block that has not yet been positioned. This method enters a loop where i runs from first_block to the last block in the list. Inside the loop, it calls the BlocksFit method to see if the blocks with indices first_block through i will fit on the current row.

After it finds a set of blocks that will not fit on the current row, the method backtracks to the previous set. If that set is empty (no blocks will fit on the row), the method returns 0.

If some blocks fit on the row, the method calls BlocksFit again for the set that fit to reposition them. The code then loops through the positioned blocks and finds their maximum Y coordinate. It updates y so the next row is positioned below this one.

Finally, the method updates first_block and returns the number off blocks that it positioned.

BlocksFit

The following code shows the BlocksFit method.

// Return true if the indicated blocks will
// fit starting at the given Y coordinate.
private bool BlocksFit(RectangleF bounds,
    List<RectangleF> obstacles, List<Block> blocks,
    int first_block, int last_block, float y)
{
    // Find the maximum top height.
    float top_height = 0;
    for (int i = first_block; i <= last_block; i++)
    {
        if (top_height < blocks[i].TopHeight)
            top_height = blocks[i].TopHeight;
    }

    // Set the baseline.
    float baseline = y + top_height;

    // Position the blocks.
    float x = bounds.X;
    for (int i = first_block; i <= last_block; i++)
    {
        Block block = blocks[i];
        block.Bounds.X = x;
        x += block.Bounds.Width;
        if (x > bounds.Right) return false;

        block.Bounds.Y = baseline - block.TopHeight;

        // See if the block intersects with an obstacle.
        RectangleF rect = block.Bounds;
        bool had_hit = true;
        while (had_hit)
        {
            had_hit = false;
            foreach (RectangleF obstacle in Obstacles)
            {
                if (obstacle.IntersectsWith(rect))
                {
                    had_hit = true;

                    // Move the block to the right.
                    rect.X = obstacle.Right;

                    // See if we are out of bounds.
                    if (rect.Right > bounds.Right) return false;
                }
            }
        }

        // Update the block's bounds.
        block.Bounds = rect;
        x = rect.Right;

        // Loop to position the next block.
    }

    // If we get this far, then we have
    // positioned all of the blocks.
    return true;
}

This method first uses a loop to find the maximum top height of the blocks in question. It then positions the row’s baseline at that distance from the row’s upper Y coordinate.

The code then loops through the blocks and tries to position each block at the next available X coordinate. The code then loops through the obstacles to see if any intersect with the block at that position. If an obstacle intersects the block, the method moves the block to the right of that obstacle. It also restarts the loop that examines the obstacles in case one of the previous obstacles now intersects the moved block.

The inner loop ends when one of two things occurs. First, if block extends beyond the right edge of the document layout area, then the blocks will not fit on this row so the method returns false.

Second, if the block does not intersect any obstacle, then it is in a safe position. In that case, the method continues its outer loop to position the other blocks in the row.

If the method successfully positions all of the blocks, it returns true.

MoveDown

The following code shows the MoveDown method.

// Move down so the block will clear at least one new obstacle.
private void MoveDown(RectangleF bounds,
    List<RectangleF> obstacles, Block block, ref float y)
{
    float min_y = y + block.Bounds.Height;
    RectangleF rect = new RectangleF(
        bounds.X, y, bounds.Width, block.Bounds.Height);
    foreach (RectangleF obstacle in obstacles)
    {
        if (obstacle.IntersectsWith(rect))
        {
            if (min_y > obstacle.Bottom)
                min_y = obstacle.Bottom;
        }
    }

    y = min_y;
}

This method creates a rectangle as tall as the next block to position and as wide as the document layout area. It then loops through the obstacles to find the smallest Y coordinate of the bottom of any obstacle that intersects with the rectangle. The code updates the reference parameter y to move past that Y value and returns.

This moves the next row past the highest obstacle that intersects the rectangle. The higher-level methods then try to position the row again. If those methods fail again, they may call MoveDown again.

Main Program

That’s all of the document layout code. The following code shows how the program generates some random obstacles and blocks.

// Obstacles.
private List<RectangleF> Obstacles;

// Blocks to flow around obstacles.
private List<Block> Blocks;

// Define some blocks.
private void Form1_Load(object sender, EventArgs e)
{
    Obstacles = new List<RectangleF>();
    Obstacles.Add(new RectangleF(0, 0, 64, 64));
    Obstacles.Add(new RectangleF(200, 50, 50, 100));
    Obstacles.Add(new RectangleF(50, 150, 100, 70));
    Obstacles.Add(new RectangleF(300, 200, 100, 64));

    // Random rand = new Random(4);
    Random rand = new Random(4);
    Blocks = new List<Block>();
    for (int i = 0; i < 25; i++)
    {
        float width = rand.Next(10, 100);
        float height = rand.Next(10, 100);
        Blocks.Add(new Block(
            new RectangleF(0, 0, width, height), height * 0.6f));
    }
}

At the form level, the code defines lists to hold obstacles and blocks. The form’s Load event handler creates some specific obstacles and then uses a Random object to generate blocks with random sizes. It sets each block’s TopHeight value to 0.6 times its height.

The program arranges the blocks in the following Paint event handler.

// Flow and draw the blocks.
private void picWriting_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

    // Flow.
    FlowBlocks(picWriting.ClientRectangle, Obstacles, Blocks);

    // Draw obstacles.
    foreach (RectangleF obstacle in Obstacles)
    {
        e.Graphics.FillRectangle(Brushes.Pink, obstacle);
        e.Graphics.DrawRectangle(Pens.Red, obstacle);
    }

    // Draw flowed blocks.
    for (int i = 0; i < Blocks.Count; i++)
    {
        Blocks[i].Draw(e.Graphics, i, picWriting.Font); 
    }
}

This code calls the FlowBlocks method to position the blocks. It then loops through the obstacles and draws them as pink rectangles.

Next, the code creates a dashed pen and a StringFormat object to center text. It then loops through the blocks. It draws each block in green, draws its baseline with the dashed pen, and draws the block’s index inside it.

Conclusion

This method is rather long, but it seems to produce a reasonable result. The picture shown at the top of the post includes some fairly large blank areas. For example, there’s a pretty big break after block 0 and below block 0. Those should be smaller with a more realistic arrangement where drop caps are on the left and a relatively large amount of text flows to the right. After you see the next two posts, you can decide whether you want to modify the document layout algorithm.

Meanwhile, download the example and experiment with it. In my next post, I’ll show how you can modify the block flow method to flow text with various fonts around obstacles.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, fonts, graphics, strings | Tagged , , , , , , , , , , , , | 1 Comment

Serialize and deserialize multiple images in files in C#

[serialize]

The example Serialize and deserialize multiple images in C# showed how you can List<Image> into a memory stream and then deserialize stream to recover the images. This example shows how you can similarly serialize a list of images into a file and then deserialize the list from that file.

This example actually includes two programs, one to serialize the images and one to deserialize them. I decided to do this in two programs to be certain that the serialization did not include the namespace of the serialization program. Some serialization methods include the serialized object’s namespace and that can make deserializing the objects harder. This example uses two programs to show that this isn’t an issue.

The previous example shows most of what you need to do to serialize and deserialize a list of images in a file. The following sections describe the key pieces of the two example programs included in the download.

Serialization


[serialize]

The howto_save_serialized_images example program displays three images that are placed in PictureBox controls at design time. When you open the File menu and select Save Serialization, the following code executes.

// Save a serialization of the images.
private void mnuFileSave_Click(object sender, EventArgs e)
{
    // Get the serialization file name.
    if (sfdSerialization.ShowDialog() == DialogResult.OK)
    {
        // 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.
        using (FileStream fs = new FileStream(
            sfdSerialization.FileName, FileMode.Create))
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(fs, input_images);
        }
    }
}

This code displays a SaveFileDialog. If you enter a file name and click Save, the code creates a List<Image> and adds the three images to it.

Next, the program creates a FileStream to create the file. It makes a BinaryFormatter and uses its Serialize method to serialize the list of images into the FileStream.

When the using block ends, the program automatically closes the FileStream.

Deserialization


[serialize]

The howto_load_serialized_images example program lets you read the images in a serialization. When you open the File menu and select Open Serialization, the following code executes.

// Load a serialization.
private void mnuFileOpen_Click(object sender, EventArgs e)
{
    // Get the serialization file name.
    if (ofdSerialization.ShowDialog() == DialogResult.OK)
    {
        // Deserialize.
        using (FileStream fs = new FileStream(
            ofdSerialization.FileName, FileMode.Open))
        {
            BinaryFormatter formattter = new BinaryFormatter();
            List<Image> output_images =
                (List<Image>)formattter.Deserialize(fs);

            // Display the images.
            const int margin = 10;
            int x = margin;
            int y = menuStrip1.Bottom + 4;
            foreach (Image image in output_images)
            {
                PictureBox pic = new PictureBox();
                pic.Location = new Point(x, y);
                pic.SizeMode = PictureBoxSizeMode.AutoSize;
                pic.Image = image;
                pic.BorderStyle = BorderStyle.Fixed3D;
                pic.Parent = this;
                x += pic.Width;
            }
        }
    }
}

This code displays an OpenFileDialog. If you select a file and click Open, the code creates a FileStream attached to that file. It then makes a BinaryFormatter and uses its Deserialize method to read the serialization from the FileStream. It converts the result into a List<Image>.

Next, the program loops through the images in the list and displays them in a sequence of PictureBox controls.

Conclusion

That’s about all there is to the example programs that serialize and deserialize images. This is one of the rare instances when the download doesn’t contain much more than the code shown here. The only important things that are missing are the two following using directives.

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

Using the techniques demonstrated by these examples and the previous post, you should be able to serialize and deserialize a list of images in any stream, whether it’s a memory stream, file stream, or something else.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in database, files, serialization | Tagged , , , , , , , , , , , , , | 3 Comments

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