Use regular expressions to rename files in a directory hierarchy in C#


example

This example extends the example Use regular expressions to rename files within a date range and that match a pattern in C# to let you rename files that match a pattern and that were modified within a date range. See the previous example for most of the details. This post describes the changes from that version.

The earlier versions of this program only searched a single directory. This version only makes one small change to let you search recursively through a directory hierarchy. When it lists the files that it will update, this version of the program uses the following code.

// Get the files that match the pattern and date range.
DirectoryInfo dir_info = new DirectoryInfo(txtDirectory.Text);
SearchOption option = SearchOption.TopDirectoryOnly;
if (radAllDirectories.Checked) option = SearchOption.AllDirectories;
FileInfo[] files = dir_info.GetFiles(txtFilePattern.Text, option);
Regex regex = new Regex(txtOldPattern.Text);

The code highlighted in blue shows the changes. The program sets the variable option to TopDirectoryOnly. Then if the All Directories radio button is checked, the code changes the option to AllDirectories.

The program then passes the option into the call to GetFiles so that method can search either the subdirectories if appropriate.

The rest of the program is unchanged. See the previous example and download this program to see all of the details.


Download Example   Follow me on Twitter   RSS feed




Posted in files, regular expressions | Tagged , , , , , , , , , , , , | Comments Off on Use regular expressions to rename files in a directory hierarchy in C#

Copy a C# project


[example]

Sometimes you might want to copy a C# project so you can save the current version or so you can modify it for another purpose. Unfortunately, when you copy a C# project, the copied program does not automatically update the project’s name. For example, you could have the howto_copy_project project in the directory my_new_project. The project, solution, namespace, and other values inside the project will keep their old name. If you open the new and original projects at the same time, it’s easy to become confused about which version is which.

To copy a C# project, copy its directory and all of the files that it contains. You do not need to copy these subdirectories:

  • bin
  • obj
  • .vs (in newer versions)

Those files are created as necessary by Visual Studio. (If you want to zip up the project to save or email to someone, be sure to remove those subdirectories to save space. You may notice that the zip files on the C# Helper web site do not include those subdirectories.)

After you copy the project, you need to update all of the references to the original project’s name. Follow these steps:

  1. Copy the program (say howto_copy_project) and all of its files into the new directory (say my_new_project). You don’t need to copy the bin, obj, or .vs subdirectories because Visual Studio creates them as needed, but if the project contains any other subdirectories, copy them, too.
  2. Open the copied project in Visual Studio.
  3. In Solution Explorer, right-click the project name, select rename, and enter the new name (my_new_project).
  4. In Solution Explorer, right-click the solution name, select rename, and enter the new name (my_new_project).
  5. In versions of Visual Studio before VS 2022:
    1. Click on the project in Solution Explorer, open the Project menu, and select the Properties command at the very bottom.
    2. Change the Assembly Name and Default Namespace to the new name (my_new_project).
    3. Still on the Properties page, click Assembly Information. Change the Title and Project to the new name (my_new_project) and click OK.
    4. Save the project.
  6. In all versions of Visual Studio:
    1. Open a source code file (such as Program.cs or MainWindow.xaml.cs) in the code editor.
    2. Find the namespace statement at the top of the file. Right-click the namespace name (howto_copy_project), select Rename, type the new name (my_new_project), and press Enter.
    3. Press Ctrl+H to open the Find and Replace dialog. Replace the original name (howto_copy_project) with the new name (my_new_project) for the current project.
  7. Save the project.

At this point the copied project should be its own entity with no signs of the original project’s name.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in C# | Tagged , , , , , , | Comments Off on Copy a C# project

Remove all event handlers from an event in C#


[example]

I found this technique on this Microsoft forum.

When you click the program’s Add button, the following code installs several event handlers.

private void btnAdd_Click(object sender, EventArgs e)
{
    btnClickMe.Click += btnClickMe_Click;
    picCanvas.Click += picCanvas_Click;
    picCanvas.MouseClick += picCanvas_MouseClick;
    picCanvas.MouseDown += picCanvas_MouseDown;
    picCanvas.MouseMove += picCanvas_MouseMove;
    picCanvas.MouseUp += picCanvas_MouseUp;
    txtOutput.AppendText("Added event handlers\r\n");
}

Each of the event handlers adds a message to the output text box. For example, the following code executes when you click on the PictureBox to the left of the Click Me button.

private void picCanvas_Click(object sender, EventArgs e)
{
    txtOutput.AppendText("picCanvas_Click\r\n");
}

See the example for more details.

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

private void btnRemove_Click(object sender, EventArgs e)
{
    RemoveEvent(btnClickMe, "EventClick");
    RemoveEvent(picCanvas, "EventClick");
    RemoveEvent(picCanvas, "EventMouseClick");
    RemoveEvent(picCanvas, "EventMouseDown");
    RemoveEvent(picCanvas, "EventMouseMove");
    RemoveEvent(picCanvas, "EventMouseUp");
    MouseIsDown = false;
    txtOutput.AppendText("Removed event handlers\r\n");
}

This code mostly calls the following RemoveEvent method, passing it the names of the controls and events that the method should remove.

// Remove all event handlers from the control's named event.
private void RemoveEvent(Control ctl, string event_name)
{
    FieldInfo field_info = typeof(Control).GetField(event_name,
        BindingFlags.Static | BindingFlags.NonPublic);

    PropertyInfo property_info = ctl.GetType().GetProperty("Events",
        BindingFlags.NonPublic | BindingFlags.Instance);

    object obj = field_info.GetValue(ctl);
    EventHandlerList event_handlers =
        (EventHandlerList)property_info.GetValue(ctl, null);
    event_handlers.RemoveHandler(obj, event_handlers[obj]);
}

The RemoveEvent method is the heart of the program. It removes all event handlers assigned to the indicated event on the given control. Here’s how I think it works. (Although it’s pretty arcane, so I’m not absolutely sure.)

The code first uses the Control type’s GetField method to get a FieldInfo object describing the event type. It then gets a PropertyInfo object holding information about the control’s events.

Next the code calls the FieldInfo object’s GetValue method to get an object representing the desired event for the target control. It uses the PropertyInfo object’s GetValue method to get a list of event handlers for the event. It finishes by calling RemoveHandler to remove the event handlers for the event.


Download Example   Follow me on Twitter   RSS feed   Donate



Posted in events | Tagged , , , , , , | Comments Off on Remove all event handlers from an event in C#

Display a colored battery status in C#

[example]

This is a minor update to the example Display battery status in a friendly way in C#. That example periodically checks the battery’s charge. It then draws textual and graphical indicators of the charge and plugged/unplugged status. That example was only for demonstration, so it drew the graphical status in four different ways.

Lately I’ve been breaking in a new laptop battery, so I wanted a way to easily keep track of the battery status. This example draws the graphical status in only one way instead of four. It’s also smaller and changes the colors of the display depending on the status. For example, as you can see from the picture, if the battery has a very small charge, it draws the charged area in red and the uncharged area in yellow.

The following code shows the part of the program that sets the colors.

// Draw the battery image.
Color bg_color = Color.Transparent;
Color outline_color = Color.Gray;
Color charged_color = Color.LightGreen;
Color uncharged_color = Color.White;
if (percent < 0.15f)
{
    outline_color = Color.Black;
    charged_color = Color.Red;
    uncharged_color = Color.Yellow;
}
else if (percent < 0.25f)
{
    outline_color = Color.Black;
    charged_color = Color.Orange;
}
picHBattery1.Image = DrawBattery(
    percent,
    picHBattery1.ClientSize.Width,
    picHBattery1.ClientSize.Height,
    bg_color, outline_color,
    charged_color, uncharged_color,
    true);

This code defines several Color variables to store the colors that the program will use to draw the graphical indicator. It then uses if else-if tests to change the colors if necessary. The program then calls the DrawBattery method described in the earlier post to draw the battery.

Download the example and see the earlier example to see additional details such as the DrawBattery method.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in drawing, graphics, system | Tagged , , , , , , , , , , | Comments Off on Display a colored battery status in C#

How to tabulate ranked voting in C#

[example]

New York City’s recent election used a ranked voting ballot. This example shows how you might find the winner in a ranked voting election.


What Is Ranked Voting?

In ranked voting (aka ranked-choice voting or preferential voting), each voter ranks the candidates into first, second, third, and other choices. To decide the winner, you repeat these steps:

  1. For each voter, find the highest ranked candidate that is still in the running and add one to that candidate’s tally.
  2. If a candidate has more than 50% of the total votes, then that candidate is the winner.
  3. If no candidate has more than 50% of the total votes, you find the candidate(s) with the fewest votes and eliminate them from the running.

You repeat those steps until you find a winner.

Note that you might eliminate multiple candidates in a single round if they have exactly the same smallest number of votes.

It is also possible to end in a tie. This happens if two or more candidates split the vote exactly evenly. For example, if there are 3,000 ballots and three remaining candidates, each of whom has exactly 1,000 votes, then you cannot determine the last place candidate to eliminate.

Both eliminating multiple candidates in a single round and a tie are very unlikely if there are many voters.

Note also that, if there are N candidates, then there can be at most N – 1 rounds. During each round, at least one candidate is eliminated, so after N – 1 rounds there can be at most one candidate remaining. (Often there will be a winner earlier.)

Why?

You can read about the pros and cons of ranked voting on Wikipedia and other places, but here’s one important advantage.

Ranked voting avoids penalizing similar candidates who split the vote. For example, suppose an election has four candidates, three of whom (Anderson, Baker, and Campbell) have similar peace-and-harmony platforms and one of whom (Zantos) is very different, running on a conquer-the-world platform. Now suppose 71% of the people prefer the peace-and-harmony platform and the votes come out as: Anderson 24%, Baker 21%, Campbell 26%, and Zantos 29%.

In a plurality voting system where the candidate with the most votes wins, Zantos would win even though 71% of the voters oppose the conquer-the-world platform.

There are several ways you could avoid an outcome where a minority candidate wins. You could have a runoff election between the two most popular candidates. In this example, Campbell would win. Note that this might not be the “best” outcome either if all of the Baker voters prefer Anderson as their second choice. In that case, a majority might prefer Anderson to Campbell, but at least they have some input in the runoff.

Ranked voting is another strategy for handling this issue. It also has the advantage that you can perform the rounds without going back to the ballot box, which takes extra time and effort.

The Ballot Class

The example uses the following Ballot class to hold a voter’s choices.

public class Ballot
{
    // Choices holds the candidate numbers in ranked order.
    public int[] Choices;
    public Ballot(int num_candidates)
    {
        Choices = Extensions.RandomArrangement(num_candidates);
    }

    // Find this ballot's top non-disqualified choice.
    public int TopChoice(bool[] disqualified)
    {
        for (int i = 0; i < disqualified.Length; i++)
        {
            int candidate = Choices[i];
            if (!disqualified[candidate]) return candidate;
        }
        return -1;
    }
}

The Choices array holds the indices of the candidates in the voter’s ranking. For example, if the array holds 1, 3, 2, 0, then the voter’s first choice is candidate 1, the second choice is candidate 3, and so forth.

The TopChoice method returns the voter’s top choice given an array showing which candidates have been disqualified in earlier rounds. For example, disqualified[2] is true if candidate number 2 has been eliminated.

This method simply loops through the voter’s choices in rank order. When it finds a candidate that has not been eliminated, it returns that candidate’s index.

The method should not fail to find a candidate, because as you will see the program will not eliminate all of the candidates.

Tabulating Votes

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

private void btnTabulate_Click(object sender, EventArgs e)
{
    int num_ballots = Ballots.Length;
    int num_candidates = Ballots[0].Choices.Length;
    int needed_to_win = (int)(num_ballots / 2.0 + 1);

    lvwVotes.Columns.Clear();
    lvwVotes.Columns.Add("Round");
    for (int i = 0; i < num_candidates; i++)
    {
        lvwVotes.Columns.Add("Can " + i.ToString());
    }
    lvwVotes.Columns.Add("Notes");
    lvwVotes.Items.Clear();

    // Make an array indicating which
    // candidates have been disqualified.
    bool[] disqualified = Enumerable.Repeat(false, num_candidates).ToArray();

    // Repeat rounds until we have a winner.
    // Note that there can be at most num_candidates - 1 rounds,
    // and round num_candidates - 1 could end in an exact tie.
    for (int round_num = 0; round_num < num_candidates - 1; round_num++)
    {
        // Count the votes.
        int[] votes = new int[num_candidates];
        foreach (Ballot ballot in Ballots)
        {
            // Add to this ballot's top candidate's total.
            votes[ballot.TopChoice(disqualified)]++;
        }

        // Display the totals.
        ListViewItem item = new ListViewItem(round_num.ToString());
        for (int candidate = 0; candidate < num_candidates; candidate++)
        {
            if (disqualified[candidate])
                item.SubItems.Add("---");
            else
                item.SubItems.Add(votes[candidate].ToString());
        }
        lvwVotes.Items.Add(item);

        // See if there is a winner.
        int winner = -1;
        for (int candidate = 0; candidate < num_candidates; candidate++)
        {
            if (votes[candidate] >= needed_to_win)
            {
                winner = candidate;
                break;
            }
        }

        if (winner >= 0)
        {
            // We have as winner!
            item.SubItems.Add(winner.ToString() + " wins!");
            break;
        }

        // Find the smallest vote total(s).
        string notes = "";
        int max_votes = int.MinValue;
        int min_votes = int.MaxValue;
        for (int i = 0; i < num_candidates; i++)
        {
            if (!disqualified[i])
            {
                if (votes[i] < min_votes) min_votes = votes[i];
                if (votes[i] > max_votes) max_votes = votes[i];
            }
        }

        if (min_votes == max_votes)
        {
            // We have a tie.
            item.SubItems.Add("Tie");
            break;
        }

        // Disqualify last place candidate(s).
        for (int i = 0; i < num_candidates; i++)
        {
            if ((!disqualified[i]) && (votes[i] == min_votes))
            {
                disqualified[i] = true;
                notes += ", x" + i.ToString();
            }
        }
        notes = notes.Substring(2);

        item.SubItems.Add(notes);
    }
}

The code gets the number of ballots and candidates, and calculates the number of votes needed to win. That number is half of the total votes plus one.

The program then clears the lvwVotes ListView control and creates columns in it to represent the candidates and a final Notes column.

The code then initializes the disqualified array so no candidate is disqualified. It then enters a loop to perform the voting rounds.

During each round, the program loops through the Ballot objects, gets their preferred candidate, and increments that candidate’s count.

Next the code loops through the candidates and displays their counts in the lvwVotes ListView control. If a candidate has been eliminated, the program displays —.

The code then determines whether there has been a winner by checking whether any candidate received the needed number of votes. If there is a winner, the program displays the winning candidate number and exits the rounds loop.

If there is no winner, the code loops through the candidates and finds the largest and smallest numbers of votes that any candidate received.

If the largest and smallest numbers of votes are the same, then all of the remaining candidates have the same number of votes, so the vote ends in a tie. The program reports that and exits the rounds loop.

If we don’t have a tie, the program loops through the candidates again and disqualifies any that have the smallest number of votes. If there are many votes, then this will probably only be one candidate. If multiple candidates have exactly the same smallest number of votes, then they will be removed together.

The program displays a brief message indicating the candidates that it removed.

Now the program continues its main loop to perform the next round of voting.

Eventually the program will either find a winner with 50% + 1 votes, or all of the remaining candidates will have the same number of votes and we end in a tie. I’m not sure how you would handle that. A runoff election among the tied candidates would probably break things loose again (because some voters will probably change their voting strategy given another chance) so the algorithm could continue.

Conclusion

Ranked voting may have some problems with special cases (like ties) and voters may be reluctant to try a new system, but it does have some advantages. For example, it avoids a separate runoff election and prevents an unpopular candidate from winning when other candidates split the vote. Of course the electoral college is a whole different issue.

As always, download the example to see additional details such as how the user interface works and how the program generates random ballots. Note that the ListView control cannot handle huge numbers of items, so don’t try to display 1 million ballots. If you want to experiment with huge numbers of voters, modify the code so it does not display them all.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms | Tagged , , , , , , | Comments Off on How to tabulate ranked voting in C#

Select rectangular areas in an image in WPF and C#


[select rectangular areas]

This is another fine example of WPF’s unofficial slogan: Twice as flexible and only five times as hard. Practically everything about this example is harder than it is in Windows forms: drawing the rectangle, selecting the area, saving the results into a file, building a menu item with a shortcut, arranging the controls, and even simply displaying the original image.

I’m going to gloss over some of those topics and focus mostly on the two issues that are most central to the example: how to select rectangular areas and how to save the result into a file. Download the example program to see additional details.

Before I get to those topics, however, you should probably see the XAML code.

XAML Code

The following XAML code builds the program’s user interface.

<Window x:Class="wpf_select_image_rectangle.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="wpf_select_image_rectangle"
    Height="410" Width="624"
    Background="LightBlue">
    
    <Window.CommandBindings>
        <CommandBinding Command="SaveAs"
            CanExecute="SaveAsCommand_CanExecute"
            Executed="SaveAsCommand_Executed" />
    </Window.CommandBindings>
    
    <Window.InputBindings>
        <KeyBinding Gesture="Ctrl+S" Command="SaveAs" />
    </Window.InputBindings>
    
    <DockPanel>
        <Menu DockPanel.Dock="Top">
            <MenuItem Header="_File">
                <MenuItem Command="SaveAs" InputGestureText="Ctrl+S" />
            </MenuItem>
        </Menu>
        <StackPanel Margin="10" Orientation="Horizontal">
            <Canvas Name="canDraw" Width="479" Height="328"
                VerticalAlignment="Top" HorizontalAlignment="Left"
                MouseDown="canDraw_MouseDown">
                <Image Name="imgOriginal"
                    HorizontalAlignment="Left" VerticalAlignment="Top" 
                    Stretch="None" Source="cows.jpg"
                    Cursor="Cross"/>
            </Canvas>
            <Image Name="imgResult" Stretch="None" Margin="10,0,0,0"
                VerticalAlignment="Top" HorizontalAlignment="Left" />
        </StackPanel>
    </DockPanel>
</Window>

In this code you can see how the program creates a command binding that binds the SaveAs command to the SaveAsCommand_CanExecute and SaveAsCommand_Executed event handlers.

The code then creates an input binding so pressing Ctrl+S triggers the SaveAs command. The code also creates the File menu’s Save As command, which also uses the Ctrl+S gesture. That gesture doesn’t actually take any action; it just makes the menu item display the text Ctrl+S. (I guess in case you want to have a menu item that displays an input gesture but doesn’t actually do anything with it. You know, for users who like bafflingly inconsistent user interfaces.)

The key controls that let you select rectangular areas are the canvas named canDraw and the Image control that it contains. The canDraw control’s size is set to the same as the image.

One way to make the Image control display an image is to use Project > Properties > Resources > Add Resource > Add Existing File, find the file, and add it to the project. Then use the Properties window to set its Build Action property to Resource. Now you can set the Image control’s Source property to the image file’s name.

Rectangle Code

The XAML code shown earlier sets the canDraw control’s MouseDown event handler. When you press the mouse down on that control, the following code executes.

private Rectangle DragRectangle = null;
private Point StartPoint, LastPoint;

private void canDraw_MouseDown(object sender, MouseButtonEventArgs e)
{
    StartPoint = Mouse.GetPosition(canDraw);
    LastPoint = StartPoint;
    DragRectangle = new Rectangle();
    DragRectangle.Width = 1;
    DragRectangle.Height = 1;
    DragRectangle.Stroke = Brushes.Red;
    DragRectangle.StrokeThickness = 1;
    DragRectangle.Cursor = Cursors.Cross;

    canDraw.Children.Add(DragRectangle);
    Canvas.SetLeft(DragRectangle, StartPoint.X);
    Canvas.SetTop(DragRectangle, StartPoint.Y);

    canDraw.MouseMove += canDraw_MouseMove;
    canDraw.MouseUp += canDraw_MouseUp;
    canDraw.CaptureMouse();
}

This code first gets the mouse position relative to the canDraw control and saves that position. It then creates a Rectangle object and sets some of its properties.

The code then adds the rectangle to the canDraw control’s Children collection. It sets the rectangle’s Left and Top attached properties to position the rectangle inside the Canvas control.

Next the event handler registers event handlers for the MouseMove and MouseUp events. It finishes by capturing the mouse for the Canvas control so future MouseMove and MouseUp events go to that control.

The following code executes when you move the mouse after you have pressed it down on the canDraw control.

private void canDraw_MouseMove(object sender, MouseEventArgs e)
{
    LastPoint = Mouse.GetPosition(canDraw);
    DragRectangle.Width = Math.Abs(LastPoint.X - StartPoint.X);
    DragRectangle.Height = Math.Abs(LastPoint.Y - StartPoint.Y);
    Canvas.SetLeft(DragRectangle, Math.Min(LastPoint.X, StartPoint.X));
    Canvas.SetTop(DragRectangle, Math.Min(LastPoint.Y, StartPoint.Y));
}

This code gets the mouse’s current position and uses that position and the start position to calculate the size and location of the rectangle. It then updates the rectangle’s size and position.

When you release the mouse button, the following code executes.

private void canDraw_MouseUp(object sender, MouseButtonEventArgs e)
{
    canDraw.ReleaseMouseCapture();
    canDraw.MouseMove -= canDraw_MouseMove;
    canDraw.MouseUp -= canDraw_MouseUp;
    canDraw.Children.Remove(DragRectangle);

    if (LastPoint.X < 0) LastPoint.X = 0;
    if (LastPoint.X >= canDraw.Width) LastPoint.X = canDraw.Width - 1;
    if (LastPoint.Y < 0) LastPoint.Y = 0;
    if (LastPoint.Y >= canDraw.Height) LastPoint.Y = canDraw.Height - 1;

    int x = (int)Math.Min(LastPoint.X, StartPoint.X);
    int y = (int)Math.Min(LastPoint.Y, StartPoint.Y);
    int width = (int)Math.Abs(LastPoint.X - StartPoint.X) + 1;
    int height = (int)Math.Abs(LastPoint.Y - StartPoint.Y) + 1;

    // Note that the CroppedBitmap object's SourceRect
    // is immutable so we must create a new CroppedBitmap.
    BitmapSource bms = (BitmapSource)imgOriginal.Source;
    CroppedBitmap cropped_bitmap =
        new CroppedBitmap(bms, new Int32Rect(x, y, width, height));
    imgResult.Source = cropped_bitmap;
    
    DragRectangle = null;
}

This code first releases the mouse capture, uninstalls the MouseMove and MouseUp event handlers, and removes the rectangle from the canDraw control.

Next the code calculates the selected area’s size and location. This time it ensures that the end point’s coordinates lie within the image’s bounds. WPF is happy to draw a rectangle that extends beyond the Canvas control’s edges, but we cannot copy parts of the image that don’t exist.

The code then displays the selected area in the CroppedBitmap object. It would be nice if you could update the CroppedBitmap object’s selected area, but that value is immutable so you cannot change it after you initially create the CroppedBitmap. Instead you must create a whole new object. The code does that and then makes the imgResult control display the cropped bitmap.

Finally, the code sets DragRectangle to null so the garbage collector can dispose of the rectangle.

To summarize this part:

  • The MouseDown event handler saves the mouse position, creates a Rectangle, and installs the MouseMove and MouseUp event handlers.
  • The MouseMove event handler updates the selection rectangle.
  • The MouseUp event handler uninstalls the MouseMove and MouseUp event handlers, makes the CroppedBitmap represent the selected area, and displays the CroppedBitmap in the imgResult control.

Image Saving Code

When you select the File menu’s Save As command (or press Ctrl+S), the following code executes.

SaveFileDialog sfdImage = new SaveFileDialog();
private void SaveAsCommand_Executed(object sender,
    ExecutedRoutedEventArgs e)
{
    sfdImage.Filter =
        "Image Files|*.bmp;*.gif;*.jpg;*.png;*.tif|All files (*.*)|*.*";
    sfdImage.DefaultExt = "png";
    if (sfdImage.ShowDialog().Value)
    {
        (imgResult.Source as CroppedBitmap).SaveImage(sfdImage.FileName);
    }
}

This code displays a save file dialog to let the user pick the image file where the selected part of the image should be stored. If the user picks a file, the code treats the imgResult control’s source as a CroppedBitmap and calls its SaveImage extension method. The following code shows that method.

// Save an image of the element into a
// graphic file with an appropriate file type.
public static void SaveImage(
    this CroppedBitmap cropped_bitmap,
    string filename)
{
    FileInfo file_info = new FileInfo(filename);
    BitmapEncoder encoder = null;
    switch (file_info.Extension.ToLower())
    {
        case ".bmp":
            encoder = new BmpBitmapEncoder();
            break;
        case ".gif":
            encoder = new GifBitmapEncoder();
            break;
        case ".jpg":
        case ".jpeg":
            encoder = new JpegBitmapEncoder();
            break;
        case ".png":
            encoder = new PngBitmapEncoder();
            break;
        case ".tif":
            encoder = new TiffBitmapEncoder();
            break;
    }
    encoder.Frames.Add(BitmapFrame.Create(cropped_bitmap));

    using (FileStream stream =
        new FileStream(filename, FileMode.Create))
    {
        encoder.Save(stream);
    }
}

This method creates a FileInfo object for the desired filename. It checks the file’s extension and sets the encoder variable to the appropriate encoder type.

The code then uses the cropper bitmap to create a bitmap frame and adds it to the encoder’s Frames list. Finally the method creates a file stream and makes the encoder save its contents into the stream.

Conclusion

This example lets you select a rectangular area on an image, view that area in a CroppedBitmap, and save the CroppedBitmap contents into an image file. Several pieces of the example are awkward due to the way WPF handles images, but probably the trickiest part was figuring out exactly how to perform those tasks.

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


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in graphics, wpf | Tagged , , , , , , , , , , , | Comments Off on Select rectangular areas in an image in WPF and C#

Use VBA code to add and remove a watermark on all pages in a Word document


[watermark]

This post shows one way that you can add and remove a watermark in a Word document. To add a watermark in this way, you add a “building block” to the header of each of the document’s sections. If headers are marked so the first page is different or if odd and even pages have different watermarks, then you need to add the watermark to each separately.

The following code adds a watermark to each page.

Sub AddWatermarks()
Dim doc As Document
Dim sec As Section
Dim hdr As HeaderFooter
Dim rng As Range
Dim strBBPath As String

' Building block names.
Const confidential_1 As String = "CONFIDENTIAL 1"
Const confidential_2 As String = "CONFIDENTIAL 2"
Const do_not_copy_1 As String = "DO NOT COPY 1"
Const do_not_copy_2 As String = "DO NOT COPY 2"
Const draft_1 As String = "DRAFT 1"
Const draft_2 As String = "DRAFT 2"
Const sample_1 As String = "SAMPLE 1"

    ' Where to find the building blocks.
    strBBPath = "C:\Users\" & (Environ$("Username")) & _
        "\AppData\Roaming\Microsoft\Document Building " & _
        "Blocks\1033\14\Built-In Building Blocks.dotx"

    ' Loop through the sections.
    Set doc = ActiveDocument
    For Each sec In doc.Sections
        ' Loop through the section's headers.
        For Each hdr In sec.Headers
            ' Set rng to the end of the header.
            Set rng = hdr.Range
            rng.Start = rng.End
            
            ' Insert the desired building block.
            Application.Templates(strBBPath). _
                BuildingBlockEntries(confidential_1).Insert _
                    Where:=rng, RichText:=True
        Next hdr
    Next sec

    ' Uncomment if it's a big file and
    ' you want to know when it's done.
    'MsgBox "Done"
End Sub

This code declares some variables and then defines strings to identify the six standard watermark types. It then loops through document’s sections. For each section, it loops through the section’s headers. It creates a Range representing the end of the header and adds the desired building block to it.

If the document is long, this can take a while, so the code finishes by displaying a message box to let you know that it is done.

The following code removes watermarks from the active Word document.

' Remove watermarks from the active document.
Sub RemoveWatermarks()
Dim sec As Section

    For Each sec In ActiveDocument.Sections
        RemoveWatermarksFromRange sec.Headers(wdHeaderFooterFirstPage).Range
        RemoveWatermarksFromRange sec.Headers(wdHeaderFooterPrimary).Range
        RemoveWatermarksFromRange sec.Headers(wdHeaderFooterEvenPages).Range
    Next sec
End Sub

This subroutine loops through the document’s sections and calls the following RemoveWatermarksFromRange subroutine for each of the three kinds of headers for each section.

' Remove shapes that have a name containing the
' string PowerPlusWaterMarkObject from this range.
Sub RemoveWatermarksFromRange(rng As Range)
Dim shape_range As Shape
    
    For Each shape_range In rng.ShapeRange
        If (InStr(shape_range.Name, "PowerPlusWaterMarkObject") > 0) Then
            shape_range.Delete
        End If
    Next shape_range
End Sub

This code loops through the range’s shapes. If a shape has a name that contains the string “PowerPlusWaterMarkObject,” then it is a watermark so the code deletes it.

Note that this code worked for me, but I have not tested it extensively. I recommend that you make a copy of your document before you run the code on it, just in case something goes wrong.

I didn’t need a custom watermark, so this code does not deal with those. If you write code to add and remove custom watermarks, or if you make other changes that you think might help someone else, please post a comment below.


Download Example   Follow me on Twitter   RSS feed   Donate



Posted in Office, VBA | Tagged , , , , , , , | 1 Comment

Graph the gamma function in C#


[gamma function]

The post Calculate the gamma function in C# uses numerical integration to calculate the gamma function. (See my book Essential Algorithms, Second Edition for information on numerical integration.)

That method works fairly well for calculating Γ(x) where x ≤ 1. In particular, its values match the factorial fairly well for some rather large values of x.

Unfortunately that method is not very accurate for values of x ≤ 0.5. For smaller values of x, it’s better to use a different method. This post shows such a method and graphs it. The graph uses some useful tools and also shows one way to draw discontinuous functions.

The Lanczos Approximation

The Lanczos approximation gives one way to approximate the gamma function. Follow that link for a description of the approximation and a basic Python implementation. For a C# translation, see this Rosetta Code entry.

This example uses the following Rosetta Code function.

private static int g = 7;
private static double[] p =
{
    0.99999999999980993,
    676.5203681218851,
    -1259.1392167224028,
    771.32342877765313,
    -176.61502916214059,
    12.507343278686905,
    -0.13857109526572012,
    9.9843695780195716e-6,
    1.5056327351493116e-7
};
private double MyGammaDouble(double z)
{
    if (z < 0.5)
        return Math.PI / (Math.Sin(Math.PI * z) * MyGammaDouble(1 - z));
    z -= 1;
    double x = p[0];
    for (var i = 1; i < g + 2; i++)
        x += p[i] / (z + i);
    double t = z + g + 0.5;
    return Math.Sqrt(2 * Math.PI) * (Math.Pow(t, z + 0.5)) * Math.Exp(-t) * x;
}

This code simply follows the approximation as described on Wikipedia.

Graphing Tools

One of the biggest issues with drawing graphics is that you sometimes want to draw something in device coordinates (pixels) at a position specified in drawing coordinates. For example, you might want to draw a circle with a radius of 3 pixels at a specific point (x, y) on the graph. You need to map the point (x, y) onto the screen, but then you can’t use the same mapping to draw the circle or it may come out stretched.

To make it easier to perform that kind if drawing operation, this example provides several useful methods. The basic approach is to draw everything in pixels. The program then uses a transformation matrix to figure out where things should be drawn.

Making the Transformation

The following MakeTransforms method creates a transformation matrix to map world (drawing) coordinates to device coordinates.

// Make transformation matrices to map between
// world coordinates to device coordinates.
public static void MakeTransforms(this Graphics gr,
    float wxmin, float wymin, float wxmax, float wymax,
    float dxmin, float dymin, float dxmax, float dymax,
    bool apply_transform,
    out Matrix transform, out Matrix inverse)
{
    RectangleF world_rect = new RectangleF(
        wxmin, wymin, wxmax - wxmin, wymax - wymin);
    PointF[] device_points =
    {
        new PointF(dxmin, dymax),
        new PointF(dxmax, dymax),
        new PointF(dxmin, dymin),
    };

    transform = new Matrix(world_rect, device_points);
    inverse = transform.Clone();
    inverse.Invert();

    if (apply_transform) gr.Transform = transform;
}

This Graphics extension method first creates a rectangle to represent the world coordinates. It then makes an array of three points that indicate where the rectangle’s upper left, upper right, and lower left corners should be mapped in device coordinates.

The method assumes that you are drawing a graph, so it inverts the Y coordinates. For example, the world coordinate rectangle’s upper left corner is at (xmin, ymin). If you were drawing that rectangle directly on the screen, that would be its upper left corner. However, in a graph the Y coordinates normally increase upward, so in world coordinate space (xmin, ymin) is the lower left corner of the coordinate area. To map that location (which the RectangleF uses as its upper left corner) to the correct location, we map it to (dxmin, dymax), the bottom left corner of the drawing area. (Yes, this is all very confusing and counterintuitive. That’s the price we pay for having pixel coordinates increase downward instead of upward.)

Having define the world coordinate rectangle and the device coordinates where it should be mapped, the code uses the rectangle and point array to define a transformation matrix. The method copies the matrix and inverts the copy, so we can map back from device to world coordinates if we like. (Although this example does not do that.)

If the method’s apply_transform parameter is true, the method sets the Graphics object’s Transform property to the transformation matrix. (This example does not do that.)

Transforming Points

If you have a transformation matrix, you can use it to transform points from one coordinate system to another. In this example, that lets you transform points from world to device coordinates.

Unfortunately the Matrix class’s TransformPoints method transforms an array if points, but this example often needs to transform one point at a time. To make that easier, the program defines the following Matrix extension method.

public static PointF TransformPoint(this Matrix transform, PointF point)
{
    PointF[] points = { point };
    transform.TransformPoints(points);
    return points[0];
}

This method simply creates an array to hold the single point passed in as a parameter. It uses the Matrix object’s TransformPoints method to transform the array and returns the result.

Drawing Tick Marks

If you apply a transformation to a Graphics object, then when you call that object’s drawing methods, the result is appropriately transformed. Things become more complicated when you want to size the object in device coordinates.

For example, suppose you want to draw five pixel long tick marks along the X and Y axes. You need to position them in world coordinates, but make them five pixels long in device coordinates.

The following WDrawTick method does that.

public static void WDrawTick(
    this Graphics gr, Matrix transform,
    Pen pen, PointF wp, float ddx, float ddy)
{
    wp = transform.TransformPoint(wp);
    gr.DrawLine(pen,
        wp.X - ddx, wp.Y - ddy,
        wp.X + ddx, wp.Y + ddy);
}

This method transforms the point wp (which stands for “world point”) into device coordinates. It then draws from that point offset by the distances (-ddx, -ddy) to that point offset by (ddx, ddy). For example, suppose you want to draw a vertical tick mark that is 6 pixels tall at the point (50, 100) in device coordinates. Then you would set ddx = 0 and ddy = 3, so the method would draw from (50 – 0, 100 – 3) to point (50 + 0, 100 + 3).

Plotting Points

The following WPlotPoint method follows a similar pattern to the one used by WDrawTick.

public static void WPlotPoint(
    this Graphics gr, Matrix transform,
    Brush brush, Pen pen, 
    PointF point, float drx, float dry)
{
    point = transform.TransformPoint(point);
    RectangleF rect = new RectangleF(
        point.X - drx, point.Y - dry,
        2 * drx, 2 * dry);
    if (brush != null) gr.FillEllipse(brush, rect);
    if (pen != null) gr.DrawEllipse(pen, rect);
}

This method first transforms the point wp into device coordinates. It then creates a rectangle centered at that point with half width (X radius) drx and half height (Y radius) dry. If the method’s brush parameter is not null, the method fills the ellipse define by the rectangle. Similarly if the method’s pen parameter is not null, the method draws the ellipse.

Drawing Text

The following WDrawString method uses the same pattern.

public static void WDrawString(
    this Graphics gr, Matrix transform,
    string text, Font font, Brush brush,
    PointF point, float ddx, float ddy, StringFormat sf)
{
    point = transform.TransformPoint(point);
    point.X += ddx;
    point.Y += ddy;
    gr.DrawString(text, font, brush, point, sf);
}

This method transforms the point where it should draw the text into device coordinates. It then draws the text at that point. The StringFormat parameter lets you decide how the text is aligned (left, right, above, below, or centered) with respect to the point.

Drawing the Curve

The basic idea is simple: make x iterate over values in some desired range, use the function to calculate the corresponding Y values, and draw lines connecting the points. There are two complicating issues here.

First, we calculate the X and Y values in world coordinates but then draw them in device coordinates. By now you can probably figure out how to do that.

Second, this particular function has some discontinuities where its value is undefined and where Y values jump abruptly from negative to positive infinity or vice versa. (See the picture at the top of the post.) We don’t really want the graph to draw a vertical line connecting those two points.

The following PlotCurve method draws a curve while handling those issues.

private void PlotCurve(Graphics gr, Matrix transform,
    Pen pen, Func<double, double> function,
    double xmin, double xmax, double dx)
{
    const double ymin = -10000;
    const double ymax = 10000;

    List<PointF> points = new List<PointF>();
    double last_y = function(xmin);
    for (double x = xmin; x <= xmax; x += dx)
    {
        // Calculate y.
        double y = function(x);
        if (y < ymin) y = ymin;
        if (y > ymax) y = ymax;

        // If y changed by too much, draw whatever we have.
        if (Math.Abs(y - last_y) > 1000)
        {
            if (points.Count > 1)
            {
                gr.WDrawLines(transform, pen, points.ToArray());
            }
            points.Clear();
        }
        points.Add(new PointF((float)x, (float)y));
        last_y = y;
    }

    // Draw any remaining points.
    if (points.Count > 1)
    {
        gr.WDrawLines(transform, pen, points.ToArray());
    }
}

The method creates a points list to hold points. It calculates the function’s Y coordinate at the first X coordinate and saves it in the variable last_y. The method then makes variable x loop from xmin to xmax.

For each x value, the code calculates the corresponding function value and makes sure it lies within a reasonable range, in this case between -10,000 and 10,000.

The code then compares the new y value to the last saved value stored in last_y. If the value has changed by more than 1000, then the function is basically vertical. That means the function has entered or crossed one of its discontinuities.

In that case, the code calls the WDrawLines extension method to draw any points that are currently in the points list and clears the list.

Next, whether it drew the previous points or not, the code adds the new Y value to the points list.

After it has finished looping through all of the X values, the code checks the points list one last time to see if it contains at least two points. If it does, the method draws it.

The following code shows the WDrawLines extension method.

public static void WDrawLines(
    this Graphics gr, Matrix transform,
    Pen pen, PointF[] wpoints)
{
    PointF[] dpoints = (PointF[])wpoints.Clone();
    transform.TransformPoints(dpoints);
    gr.DrawLines(pen, dpoints);
}

Like the drawing methods described earlier, this method transforms its data from world to device coordinates and then uses the result to draw. This code creates a copy of the wpoints array so it doesn’t mess up the original array. (That wouldn’t hurt this example because the array passed into this method isn’t used again later, but it’s a good practice.) The code then uses the transformation matrix to transform the points into device coordinates and draws the points.

Conclusion

The main program performs a lot of graph drawing stuff. It draws the axes, tick marks, and tick mark labels. It uses the numerical integration method described by the previous example to draw the gamma function in red. It then uses the Lanczos approximation to draw the curve again in green.

If you compare the picture at the top of the post to the following picture at Wikipedia, you can see that the green curve looks pretty good. Click the image below to go to the gamma function’s Wikipedia entry.



By Alessio Damato – own work  This W3C-unspecified plot was created with Gnuplot., CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=365942

The Γ(x) = (x – 1)! for positive integers, but it is also defined for the real numbers that are not non-positive integers (integers that are negative or zero). In fact, the gamma function is even defined for all complex numbers except for the non-positive integers. The following picture shows a three-dimensional plot of the absolute value of the gamma function for complex values.



By Geek3 – Own work, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=5156881

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


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, graphics, mathematics | Tagged , , , , , , , , | Comments Off on Graph the gamma function in C#

Calculate the gamma function in C#


[gamma function]

The gamma function, represented by the capital Greek letter gamma Γ, was derived by Daniel Bernoulli for complex numbers with a positive real part. The interesting thing about the gamma function is that Γ(n + 1) = n! for integers n greater than zero. The function is continuous for positive integers, so it defines interpolated factorial values for non-integers. For example, 3! = Γ(3 + 1) = 6 and 4! = Γ(4 + 1) = 12. If you want to know what 3.5! would be, you can calculate Γ(3.5 + 1) ≈ 11.631728396567.

Bernoulli defined the gamma function using the following improper integral.


[gamma function]

Calculating the Gamma Function

Bernoulli’s integral doesn’t have a closed-form solution, but the following function approximates its value.

// Integrate: x^(z-1)*e^(-x) dx from 0 to infinity.
private double Gamma(double z, double dx, int num_slices)
{
    double result = 0;
    double x = 0;
    for (int i = 0; i < num_slices; i++)
    {
        double new_term = Math.Pow(x, z - 1) * Math.Exp(-x);
        if (double.IsNaN(new_term)) break;
        if (double.IsInfinity(new_term)) break;
        result += new_term;
        x += dx;
    }
    return result * dx;
}

[Essential Algorithms]

This function basically adds up num_slices slices of curve that are dx thick. (This is covered in my book Essential Algorithms, Second Edition.) To get a good result, use a small value for dt and a large value for num_slices. If you use too many slices, however, rounding errors will add up so you may not improve the resulting accuracy.

The method first initializes result to zero. It then loops through the slices, calculating the height of the function for each slice. When x is big enough, a slice’s height may turn out to be not a number (NaN) or infinity, at least as far as the double data type is concerned. In that case, the code breaks out of its loop and stops considering new slices because further values will not be helpful.

After is has added up all of the slices, the code multiplies the total height by dx to convert the heights into areas and returns the result.

There are other ways to approximate the gamma function. Believe it or not, this one seemed simplest. See Wikipedia for details.

Displaying Gamma Function Values

The example program uses the following code to display factorial values and their corresponding gamma function values.

private void btnCalculate_Click(object sender, EventArgs e)
{
    lvwValues.Items.Clear();
    Cursor = Cursors.WaitCursor;

    int minx = int.Parse(txtMinX.Text);
    int maxx = int.Parse(txtMaxX.Text);
    double dt = double.Parse(txtDt.Text);
    int num_slices = int.Parse(txtNumSlices.Text);
    for (int x = minx; x <= maxx; x++)
    {
        double x_factorial = Factorial(x);
        if (double.IsInfinity(x_factorial)) break;
        double gamma_x = Gamma(x + 1, dt, num_slices);
        double difference = Math.Abs(gamma_x - x_factorial);
        double percent_difference = difference / gamma_x * 100.0;
        lvwValues.AddRow(
            x.ToString("G4"),
            x_factorial.ToString("G4"),
            gamma_x.ToString("G4"),
            difference.ToString("G4"),
            percent_difference.ToString("G4"));
    }
    lvwValues.AutoSizeColumns();
    Cursor = Cursors.Default;
}

This code gets the parameters that you entered on the form and then loops through the desired X values. For each value, the code first calculates the factorial. If that value is too big to fit in a double, the code breaks out of its loop. (Because it won’t be able to calculate larger factorial values, either.)

Next the code calls the gamma function for the value plus one, calculates the difference between the factorial and the gamma function, and calculates the percentage difference. It then uses the AddRow extension method (described shortly) to add the values to the program’s ListView control. You can see from the picture at the top of the post that the error is less than one with the given parameters for values up to 15! = Γ(15 + 1).

ListView Extensions

This example uses two useful ListView control extensions. The following AddRow extension adds an item and subitems to create a row in the ListView control.

// Add an item and subitems to a ListView.
public static void AddRow(this ListView lvw,
    string item, params string[] subitems)
{
    ListViewItem list_view_item = lvw.Items.Add(item);
    foreach (string subitem in subitems)
        list_view_item.SubItems.Add(subitem);
}

The AddRow extension uses its first parameter to create a new ListView item. It then loops through its other parameters and adds them as subitems.

The following code automatically sizes a ListView control’s column widths.

// Autosize all columns.
public static void AutoSizeColumns(this ListView lvw,
    params string[] values)
{
    foreach (ColumnHeader column in lvw.Columns)
        column.Width = -2;
}

This code simply loops through the ListView columns and sets their widths to -2. That tells each column to set its width so it is big enough to hold its values.

Conclusion

The gamma function gives you a new way to calculate factorials, but the obvious way of multiplying integers together is much faster. The real reason why the gamma function is interesting is that it lets you calculate factorials for non-integer values.

The program can calculate values up to 170!. Values larger than that are too big for the double data type.

If you look at the picture at the top of the post, you’ll see that the differences between the factorial and gamma function are quite small. As the values grow larger, so do the errors. The following picture shows the results between 125! and 141!.


[gamma function]

Notice that the first errors are large but still a small percentage of the values. For example, the error in calculating 125! is more than 1×10196, but it’s less than 0.0000000000068% of the value 125!.

For larger values, the percent error quickly grows much larger. At those larger values, rounding errors dominate so you can’t reduce the errors too much if you use the double data type.

Download the example to experiment with it. If you like, try implementing some of the other ways for calculating the gamma function and see which give the best results.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, mathematics | Tagged , , , , , , , | 3 Comments

Find rectangles defined by a side and aspect ratio in C#

[find rectangles]

You probably haven’t needed to make your program draw rectangles that are defined by specifying one of its sides. I haven’t either, but I want this for another program that I plan to write.

Of course a single side isn’t quite enough to completely determine a rectangle. To use this example, you also need to specify the rectangle’s desired aspect ratio. (The width / height ratio.)

This program performs several tasks such as letting you draw a line segment and reading the aspect ratio either as a floating point value or in a width:height format. Those details aren’t terribly interesting, however, so I won’t cover them here. Download the example to see then in all their glory. Here I’m just going to explain how to find rectangles from the edge segment and aspect ratio.

There are two main tasks that the program must perform to find rectangles defined by an edge and an aspect ratio. First, it must find edges that make right angles with the original segment. Second, it must make those edges the correct lengths to give the desired aspect ratio.

Perpendicular Vectors

[example]

I’ve covered this before (and probably will again), but it’s worth repeating. If you have a vector <vx, vy>, then the vectors <vx, -vy> and <-vx, vy> are perpendicular to, and have the same length as, the original vector.

In the picture on the right, the red vector is the original. Note that I’ve chosen a vector where vx and vy are both positive to reduce confusion. (Remember that Y coordinates increase downward.) This all works if vx or dy is negative, but it’s a bit more confusing.

If you look at the picture, you can see how the blue and green vectors are defined. Note that the blue vector <vx, -vy> points to the right and the green vector <-vx, vy> points to the left as you look down the length of the red original vector.

Setting Edge Lengths

After we find a vector perpendicular to the given edge, we need to figure out how far to go in that direction to define the two adjacent sides. We can use the aspect ratio to do that.

Suppose the given edge has length M and let N represent the length of the adjacent edges. Then we know that either M / N = aspect_ratio or N / M = aspect_ratio, depending on whether you consider the given edge as along the rectangle’s length or height. We can solve those equations for N to give two possible values for the length of the adjacent edges.

N1 = M / aspect_ratio
N2 = M * aspect_ratio

Now suppose the given edge has vertices p1 and p2, and the rectangle’s remaining vertices are p3 and p4. We can use the perpendicular vectors to find points p3 and p4.

For example, consider the first solution for N1. If you take a vector of length M and divide its X and Y components by the aspect ratio, you get a new vector pointing in the same direction but with length N1 as desired. Similarly you can multiply the vector’s components by the aspect ratio to get a vector pointing in the same direction but with length N2.

[example]

Now add the scaled vector’s components to the coordinates of point p1 to get point D as shown in the picture on the right. Similarly add the scaled vector to point B to get point C. That gives you the four points that define one of the rectangles.

Repeat this process for the two possible values for N1 and N2 and for the two perpendicular directions <vx, -vy> and <-vx, vy> to get a total of four rectangles defined by the original side and the aspect ratio.

FindRectangles

The following FindRectangles method uses the technique described in the preceding section to find rectangles defined by a side and aspect ratio.

private List<PointF[]> FindRectangles(float aspect_ratio,
    PointF p1, PointF p2)
{
    // Get the vector p1 --> p2.
    float vx = p2.X - p1.X;
    float vy = p2.Y - p1.Y;
    if (Math.Sqrt(vx * vx + vy * vy) < 0.1) return null;

    PointF p3, p4;
    List<PointF[]> result = new List<PointF[]>();
    float perp_x, perp_y;

    // Make rectangle 1.
    perp_x = vy * aspect_ratio;
    perp_y = -vx * aspect_ratio;
    p3 = new PointF(p2.X + perp_x, p2.Y + perp_y);
    p4 = new PointF(p1.X + perp_x, p1.Y + perp_y);
    result.Add(new PointF[] { p1, p2, p3, p4 });

    // Make rectangle 2.
    p3 = new PointF(p2.X - perp_x, p2.Y - perp_y);
    p4 = new PointF(p1.X - perp_x, p1.Y - perp_y);
    result.Add(new PointF[] { p1, p2, p3, p4 });

    // Make rectangle 3.
    perp_x = vy / aspect_ratio;
    perp_y = -vx / aspect_ratio;
    p3 = new PointF(p2.X + perp_x, p2.Y + perp_y);
    p4 = new PointF(p1.X + perp_x, p1.Y + perp_y);
    result.Add(new PointF[] { p1, p2, p3, p4 });

    // Make rectangle 4.
    p3 = new PointF(p2.X - perp_x, p2.Y - perp_y);
    p4 = new PointF(p1.X - perp_x, p1.Y - perp_y);
    result.Add(new PointF[] { p1, p2, p3, p4 });

    return result;
}

The method takes as parameters the aspect ratio and two points that define the initial edge. It returns a list of arrays of points. Each array of points contains the four points that define a rectangle.

The code starts by finding the vector between the two known points.

To make the first rectangle, it switches the vx and vy components to get a perpendicular vector and multiples the components by the aspect ratio to get a vector with the necessary length N2. It then adds the vector’s components to the initial points p1 and p2 to find the rectangle’s remaining two points p3 and p4.

The method adds the points to an array and adds the array to the result list.

The code then repeats those steps to generate the other three rectangles and add them to the result list. When it has defined all three rectangles, the method returns the list.

Drawing the Rectangles

The last part of the example that I’m going to describe is the code that draws the rectangles. The program uses the following code to store various drawing values.

private PointF Point1, Point2;
private List<PointF[]> Rectangles = new List();

private Brush[] RectBrushes =
{
    new SolidBrush(Color.FromArgb(128, Color.Red)),
    new SolidBrush(Color.FromArgb(128, Color.Red)),
    new SolidBrush(Color.FromArgb(128, Color.Blue)),
    new SolidBrush(Color.FromArgb(128, Color.Blue)),
};
private Pen[] RectPens =
{
    Pens.Red,
    Pens.Red,
    Pens.Blue,
    Pens.Blue,
};

The values Point1 and Point2 are the endpoints of the initial edge that you draw. The program uses the FindRectangles method to store the found rectangles in the Rectangles list. The RectBrushes and RectPens arrays hold the brushes and pens that the program uses to draw the rectangles.

The following Paint event handler does the drawing.

private void picCanvas_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.Clear(picCanvas.BackColor);
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    
    for (int i = 0; i < Rectangles.Count; i++)
    {
        e.Graphics.FillPolygon(RectBrushes[i], Rectangles[i]);
        e.Graphics.DrawPolygon(RectPens[i], Rectangles[i]);
    }

    if (Point1 != Point2)
    {
        e.Graphics.DrawLine(Pens.Black, Point1, Point2);
    }
}

After some setup, the code loops through the rectangles in the Rectangles list, filling and then outlining each. If you look at the RectBrushes array, you’ll see that the program first draws two translucent red rectangles and then draws two translucent blue rectangles. if you look at the picture at the top of this post, you’ll see that the two first rectangles come out purple (blue on top of red). The third and fourth rectangles are blue and they cover the first two rectangles.

Conclusion

As I said, you may never need to find rectangles defined by an edge and an aspect ratio. In my next post I’ll use this technique to find a special kind of rectangle that is related to an interesting mathematical problem, the solution to which demonstrates the most understandable use of topology that I’ve seen.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in drawing, geometry, graphics | Tagged , , , , , , , , , | 3 Comments