This is another performance testing post (the last for a while, I promise). Assembly language usually has statements comparable to x++, x--, x +=, and other operators that add or subtract to a value and store the result in the same value, so it makes some sense that those statements might be faster than statements like x = x + 3 in C#. This example uses the following code to test the speeds of the statements x = x + 1, x++, x = x + 3, and x += 3.
// Trials using x = x + 1.
x = 1;
start_time = DateTime.Now;
for (long i = 0; i < num_trials; i++)
{
x = x + 1;
}
elapsed = DateTime.Now - start_time;
lblXPlus1.Text = elapsed.TotalSeconds.ToString("0.00") + " seconds";
Refresh();
// Trials using x++.
x = 1;
start_time = DateTime.Now;
for (long i = 0; i < num_trials; i++)
{
x++;
}
elapsed = DateTime.Now - start_time;
lblXIncr.Text = elapsed.TotalSeconds.ToString("0.00") + " seconds";
Refresh();
// Trials using x = x + 3.
x = 1;
start_time = DateTime.Now;
for (long i = 0; i < num_trials; i++)
{
x = x + 3;
}
elapsed = DateTime.Now - start_time;
lblPlus3.Text = elapsed.TotalSeconds.ToString("0.00") + " seconds";
Refresh();
// Trials using x += 3.
x = 1;
start_time = DateTime.Now;
for (long i = 0; i < num_trials; i++)
{
x += 3;
}
elapsed = DateTime.Now - start_time;
lblPlusEquals3.Text = elapsed.TotalSeconds.ToString("0.00") + " seconds";
Refresh();
The code is straightforward. It simply loops through a series of tests evaluating the desired statements.
If you look closely at the figure, you'll see that all of these statements have very close to the same performance. That means it doesn't matter which type of statement you use to perform an operation. Use the one that you find easiest to read and understand and don't worry about any tiny differences in performance.
Also note that all of these statements are blindingly fast. The difference in speed between the fastest and slowest tests (and these vary when you rerun the tests) is less than a quarter second in 1 billion trials.
The conditional operator ?: looks confusing enough that some programmers assume it must be more efficient than a comparable but longer if-else statement. This example uses the following code to compare the speeds of the conditional operator and an if-else statement.
// Trials using if.
x = 1;
start_time = DateTime.Now;
for (long i = 0; i < num_trials; i++)
{
if (x % 2 == 0) x = x + 1;
else x = x + 3;
}
elapsed = DateTime.Now - start_time;
lblIfTime.Text = elapsed.TotalSeconds.ToString("0.00") + " seconds";
Refresh();
// Trials using ?:.
x = 1;
start_time = DateTime.Now;
for (long i = 0; i < num_trials; i++)
{
x = (x % 2 == 0) ? x + 1 : x + 3;
}
elapsed = DateTime.Now - start_time;
lblConditionalTime.Text = elapsed.TotalSeconds.ToString("0.00") + " seconds";
Refresh();
The code records the current time in the start_time variable and then enters a loop. The loop determines whether the variable x is odd or even and adds either 1 or 3 to it.
When the loop finishes, the program subtracts start_time from the current time and displays the number of seconds that have elapsed.
The program then repeats the same steps using the conditional operator instead of an if-else statement.
You can see from the figure that the difference between the two approaches is negligible. In this trial, after 100 million tests the difference in time was only 30 milliseconds. Variations in the operating system are more significant so if you run the tests several times you will get different results and sometimes the if-else statement will be faster.
The moral is you should use whichever of these statements seems easier to read and understand and not worry about performance. For a very short statement, you may find ?: more compact than if-else. Often ?: is confusing so I usually use if-else instead.
(Note that it's a good thing that the performance of the two statements is the same. It means the C# code is being converted into comparable code before execution. It would be bad if Visual Studio compiled one of these two equivalent statements into something that was less efficient than the other. But it's also useful to check.)
// Pixellate the indicated rectangle.
private void PixellateRectangle(Rectangle rect)
{
// Restrict the rectangle to fit on the image.
int bm_wid = CurrentBitmap.Width;
int bm_hgt = CurrentBitmap.Height;
rect = Rectangle.Intersect(rect,
new Rectangle(0, 0, bm_wid, bm_hgt));
// Process the rectangle.
const int box_wid = 8;
using (Graphics gr =
Graphics.FromImage(CurrentBitmap))
{
int start_y = box_wid * (int)(rect.Top / box_wid);
int start_x = box_wid * (int)(rect.Left / box_wid);
for (int y = start_y; y <= rect.Bottom; y += box_wid)
{
for (int x = start_x; x <= rect.Right; x += box_wid)
{
// Pixellate the area with upper left corner (x, y).
// Get the average of the pixels' color component values.
int total_r = 0, total_g = 0, total_b = 0, num_pixels = 0;
for (int dy = 0; dy < box_wid; dy++)
{
if (y + dy >= bm_hgt) break;
for (int dx = 0; dx < box_wid; dx++)
{
if (x + dx >= bm_wid) break;
Color pixel_color =
CurrentBitmap.GetPixel(x + dx, y + dy);
total_r += pixel_color.R;
total_g += pixel_color.G;
total_b += pixel_color.B;
num_pixels++;
}
}
byte r = (byte)(total_r / num_pixels);
byte g = (byte)(total_g / num_pixels);
byte b = (byte)(total_b / num_pixels);
Color new_color = Color.FromArgb(255, r, g, b);
// Give all pixels in the box this color.
using (Brush br = new SolidBrush(new_color))
{
gr.FillRectangle(br, x, y, box_wid, box_wid);
}
}
}
// Refresh to show the new image.
picImage.Image = CurrentBitmap;
picImage.Refresh();
}
}
This method first intersects the selected rectangle with the picture's bounds. If the user drags and then releases the mouse off the edge of the picture, the rectangle will include points off of the picture and there's no point in trying to process them.
Next the code sets the constant box_wid to 8. This is the size of the pixellated boxes. You can change this value to make the boxes bigger or smaller.
The form-level variable CurrentBitmap holds the current picture. This is the original image loaded from a file with some areas possibly already pixellated. The program creates a Graphics object associated with that image to draw on it.
Next the program calculates the X and Y coordinates in the image where it will start pixellating. It divides the target rectangle's coordinates by box_wid, uses (int) to truncate the result, and then multiplies it by box_wid. That makes the starting X and Y coordinate the largest multiple of box_wid less than or equal to the target rectangle's upper left corner. The code does this so the pixellations of different rectangles will line up nicely. If you click and drag to pixellate two areas on a picture and those areas overlap, their pixellated boxes will line up.
Next the code loops over the pixels in the target rectangle. For each box_wid X box_wid area, the code adds up all of the pixels' color components and divides by the number of pixels within the box to get an average color value. The code then fills the box with the average color.
This piece of code is a bit long but not too complicated. The only thing worth note here is that the code uses the break statement to break out of for loops if it is processing a pixel that lies outside of the image. For example, consider the outer dy loop. The variable dy makes the code consider rows of pixels below the starting row with Y coordinate y. If start_y is near the bottom edge of the picture, then when dy is big enough y + dy will be off of the picture. In that case the program uses a break statement to end the dy loop because pixels for this value of dy and all later values of dy will lie outside of the picture. The code uses break similarly when dx moves beyond the right edge of the picture.
Usually the debugger lets you examine how a program works quite effectively but sometimes it's useful to be able to record an event for later study. For example, when you're working with mouse events, stopping execution at a break point can often mess up the events that the program is trying to track. In that case you may want to record the event information in a file and look at it later.
This example uses the following simple Logger class to record text in a file.
public static class Logger
{
// Calculate the log file's name.
private static string LogFile =
Environment.GetFolderPath(Environment.SpecialFolder.Desktop) +
"\\Log.txt";
// Write the current date and time plus
// a line of text into the log file.
public static void WriteLine(string txt)
{
File.AppendAllText(LogFile,
DateTime.Now.ToString() + ": " + txt + "\n");
}
// Delete the log file.
public static void DeleteLog()
{
File.Delete(LogFile);
}
}
The class and its methods are declared static so the main program can use its methods without creating an instance of the class, much as you can use the Console and Debug classes.
The class begins by defining a string representing the name of the log file. This example names the file Log.txt and uses the Environment class's GetFolderPath method and SpecialFolder enumeration to place the file on the current user's desktop.
The class's WriteLine method uses File.AppendAllText to add the current time plus a string to the log file.
The DeleteLog method deletes the log file so the program can start a new one.
The following code shows the main program that demonstrates the Logger class.
// Start with a fresh log file.
private void Form1_Load(object sender, EventArgs e)
{
Logger.DeleteLog();
}
// Record MouseEnter and MouseLeave events.
private void picVolleyball_MouseEnter(object sender, EventArgs e)
{
Logger.WriteLine("MouseEnter");
}
private void picVolleyball_MouseLeave(object sender, EventArgs e)
{
Logger.WriteLine("MouseLeave");
}
When the form loads, the program uses the DeleteLog method to delete the existing log file and start a new one.
This example logs MouseEnter and MouseLeave events as the mouse moves over the picVolleyball PictureBox. It simply writes a message into the log indicating which event fired.
There are lots of customizations you could make to this example. You could put the log file in locations other than the desktop. You could give the file a different name or let the program set the name at run time so different programs could have their own log files. You could even make the Logger class non-static so a program could use more than one Logger object to write into multiple log files. You could also change the format of the date and time written into the file. For example, this example records the date and time to the minute. If you're tracking events that occur very closely together, you may want to display only the time and to the 10th of a second.
However, the simple approach shown here is good enough for most debugging situations.
Note that appending to the file does take some time so you won't want to do it many times during a fast procedure. For example, if the user must move the mouse back and forth quickly across a PictureBox, you may notice a slow down if you log every MouseMove event.
(Note: Before you can use the ShadedEllipse control in the example program, you must build the solution. So after you download the example, build it before you try to view the form.)
This example walks through building a control that draws a smoothly shaded ellipse containing text.
First start by creating a normal Windows Forms Application. Then use the Project menu's Add New Item command to add a new Custom Control to the project. Name it ShadedEllipse.
Initially the control opens in a designer, which is kind of silly because the designer cannot display the new control. Switch to code view.
Add whatever code the new control needs. This control inherits most of its properties and methods from the Control class. For example, it inherits its ForeColor, BackColor, and Text properties. It also inherits basic geometry such as the ClientSize and ClientRectangle properties.
This example uses four new pieces of code: property initialization, a TextAlign property, an overridden OnPaint method, and an overridden OnTextChanged method.
The first piece of code initializes the control's properties when it is created. The following constructor does this.
public ShadedEllipse()
{
InitializeComponent();
// Set initial properties.
Font = new Font("Times New Roman", 12, FontStyle.Bold);
BackColor = Color.Green;
}
This code simply sets the control's Font and BackColor properties so the control initially looks nice.
The second piece of code is the following TextAlign property, which determines how text is aligned within the control.
private ContentAlignment _TextAlign = ContentAlignment.MiddleCenter; ///This code uses a private variable to store the property's value. The XML comment starting with /// gives IntelliSense information that it can display about the property when a programmer is using the control in the code editor. The Description attribute determines the text displayed at the bottom of the Properties window when this property is selected at design time. Finally the property's code simply gets and sets the property's value in the private variable. The third piece of code is the OnTextChanged method. At design time, if the programmer changes the control's properties in the Properties window, the control receives a Paint event and redraws itself, at least for most properties. If the programmer changes the Text property, however, the control does not automatically repaint. To make it redraw itself to show the new text, the control uses the following overridden version of OnTextChanged./// Determines the position of the text within the control. /// [Description("Determines the position of the text within the control.")] public ContentAlignment TextAlign { get { return _TextAlign; } set { _TextAlign = value; Refresh(); } }
// Redraw.
protected override void OnTextChanged(System.EventArgs e)
{
Refresh();
}
This code simply calls Refresh to make the control redraw itself.
The final new piece of code is the following OnPaint method, which does all of the really interesting work to draw the control.
// Draw the control.
protected override void OnPaint(PaintEventArgs e)
{
// Draw smoothly.
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
// Make an elliptical clipping region.
GraphicsPath path = new GraphicsPath();
path.AddEllipse(ClientRectangle);
Region clipping_region = new Region(path);
Region = clipping_region;
// Draw the background.
using (Brush br = new LinearGradientBrush(
new Point(0, 0),
new Point(ClientSize.Width, ClientSize.Height),
Color.White, BackColor))
{
e.Graphics.FillEllipse(br, ClientRectangle);
using (Pen pen = new Pen(BackColor, 4))
{
e.Graphics.DrawEllipse(pen, ClientRectangle);
}
}
// Draw the text.
using (StringFormat string_format = new StringFormat())
{
SetStringFormatFromContentAlignment(TextAlign, string_format);
using (Brush br = new SolidBrush(ForeColor))
{
e.Graphics.DrawString(Text, Font, br, ClientRectangle, string_format);
}
}
}
This code starts by setting some Graphics parameters to draw smoothly. It then makes an elliptical clipping region so the control won't draw outside of its elliptical bounds.
Next the code makes a LinearGradientBrush that shades from white in the upper left corner to the control's BackColor in the lower right corner. It then fills the ellipse and outlines it with a pen of thickness 4. Half of the pen is clipped by the clipping region so the result is a border that's about 2 pixels wide.
The code then calls the SetStringFormatFromContentAlignment method described shortly to set a StringFormat object's alignment properties. The code creates an appropriate brush and draws the text.
The following code shows the SetStringFormatFromContentAlignment method.
// Set the StringFormat's alignment properties
// appropriately for this ContentAlignment value.
private void SetStringFormatFromContentAlignment(ContentAlignment text_align, StringFormat string_format)
{
// Set the horizontal alignment.
switch (TextAlign)
{
case ContentAlignment.TopCenter:
case ContentAlignment.TopLeft:
case ContentAlignment.TopRight:
string_format.LineAlignment = StringAlignment.Near;
break;
case ContentAlignment.BottomCenter:
case ContentAlignment.BottomLeft:
case ContentAlignment.BottomRight:
string_format.LineAlignment = StringAlignment.Far;
break;
default:
string_format.LineAlignment = StringAlignment.Center;
break;
}
// Set the vertical alignment.
switch (TextAlign)
{
case ContentAlignment.BottomLeft:
case ContentAlignment.TopLeft:
case ContentAlignment.MiddleLeft:
string_format.Alignment = StringAlignment.Near;
break;
case ContentAlignment.BottomRight:
case ContentAlignment.TopRight:
case ContentAlignment.MiddleRight:
string_format.Alignment = StringAlignment.Far;
break;
default:
string_format.Alignment = StringAlignment.Center;
break;
}
}
This method simply uses two switch statements to set the StringFormat object's Alignment and LineAlignment properties so the object aligns text properly.
There's one final piece to the new control: its ToolboxBitmap. To set the bitmap, create a 16x16 pixel bitmap file. Set the pixel in the lower left corner to the color that you want displayed as transparent in the toolbox.
Next add the bitmap to the project and set its Build Action property to Embedded Resource.
Now add a ToolboxBitmap attribute to the control's class as shown in the following code.
[ToolboxBitmap(typeof(ShadedEllipse), "ShadedEllipseTool.bmp")]
public partial class ShadedEllipse : Control
{
...
}
The attribute's first parameter gives a type that is in the assembly containing the bitmap. The second parameter is the name of the bitmap file.
Use the Debug menu's Build command to build the solution and you're almost ready to go.
I've had a lot of trouble getting the Toolbox to display the control's bitmap. When you build the control, it tends to display the default bitmap instead of the one you want. To fix that, right-click in the Toolbox section containing the control, pick Choose Items, and uncheck the box next to the control to remove it. Next right-click the Toolbox section again and pick Choose Items. This time click the Browse button and select the control's compiled assembly (probably in the project's bin\Debug or bin\Release directory).
Now that the control is built, you should be able to add instances of it to the Windows Forms application. Change its BackColor, ForeColor, Text, and TextAlign properties to see what happens.
Many applications need standard File, Edit, Tools, and Help menus. Visual Studio provides a simple way to build standard menu items. You can then edit the menu to add new items and remove items that you don't need. Visual Studio initially gives the menu items unwieldy names like undoToolStripMenuItem and indexToolStripMenuItem so you may also want to change them.
To create the standard menu items, add a MenuStrip to the form. In the Form Designer, select the MenuStrip and click its smart tag (the little right-pointing triangle in its upper right corner) to open the smart tag dialog shown in the figure, and click the Insert Standard Items link.
The following figure shows the menus that are created.
The example Draw a nested series of golden rectangles in C# draws nested rectangles and connects their corners to make a square "spiral." This example makes a smooth spiral.
If you check the Circular Spiral box, the program approximates the phi spiral by using circular arcs. For each square that is removed from a rectangle, it uses the following code to draw an arc connecting two of the square's corners.
if (chkCircularSpiral.Checked)
{
// Draw a circular arc from the spiral.
RectangleF rect;
switch (orientation)
{
case RectOrientations.RemoveLeft:
rect = new RectangleF(
(float)x, (float)y, (float)(2 * hgt), (float)(2 * hgt));
gr.DrawArc(Pens.Green, rect, 180, 90);
break;
case RectOrientations.RemoveTop:
rect = new RectangleF(
(float)(x - wid), (float)y, (float)(2 * wid), (float)(2 * wid));
gr.DrawArc(Pens.Green, rect, -90, 90);
break;
case RectOrientations.RemoveRight:
rect = new RectangleF(
(float)(x + wid - 2 * hgt),
(float)(y - hgt), (float)(2 * hgt), (float)(2 * hgt));
gr.DrawArc(Pens.Green, rect, 0, 90);
break;
case RectOrientations.RemoveBottom:
rect = new RectangleF((float)x, (float)(y + hgt - 2 * wid),
(float)(2 * wid), (float)(2 * wid));
gr.DrawArc(Pens.Green, rect, 90, 90);
break;
}
}
This code simply draws an appropriate arc depending on the position of the square inside its rectangle.
If you check the True Spiral box, the program draws a logarithmic spiral with growth factor φ so its radius increases by a factor of the golden ratio φ for every quarter turn. (Actually I wonder if the growth factor shouldn't be the factor by which it grows in a complete circle.)
In any case, the program uses the following code to draw a logarithmic spiral.
if (chkTrueSpiral.Checked && points.Count > 1)
{
// Draw the true spiral.
PointF start = points[0];
PointF origin = points[points.Count - 1];
float dx = start.X - origin.X;
float dy = start.Y - origin.Y;
double radius = Math.Sqrt(dx * dx + dy * dy);
double theta = Math.Atan2(dy, dx);
const int num_slices = 1000;
double dtheta = Math.PI / 2 / num_slices;
double factor = 1 - (1 / phi) / num_slices * 0.78; //@
List new_points = new List();
// Repeat until dist is too small to see.
while (radius > 0.1)
{
PointF new_point = new PointF(
(float)(origin.X + radius * Math.Cos(theta)),
(float)(origin.Y + radius * Math.Sin(theta)));
new_points.Add(new_point);
theta += dtheta;
radius *= factor;
}
gr.DrawLines(Pens.Blue, new_points.ToArray());
}
This code sets the spiral's outermost point to be the first point used by the square spiral. It uses the last point found (where the rectangles become vanishingly small) as the spiral's origin.
Next the code calculates the factor by which it will reduce the radius for each change in angle dtheta. With a change in angle of π / 2, the radius should ideally decrease by a factor of 1 - (1 / φ). The program divides both dtheta and this factor by num_slices to make a smooth curve.
I honestly don't know why I need the factor of 0.78 here to make the curve fit the rectangles well. If you leave it out, the curve draws a nice spiral but lies well inside the rectangles. If you have any ideas, let me know.
After setting up the parameters, the program enters a loop where it generates a new point on the spiral and multiplies the spiral's radius by the scale factor to move to the next point. It continues the loop until the radius becomes too small to see and then connects the points.
With the mystery factor of 0.78, the spiral does a good job of fitting the rectangles. If you draw both spirals at the same time, you'll see that the circular approximation is remarkably close to the true spiral.
Download the example program to see the rest of the code.
This example extends the previous example
Apply filters to images to perform edge detection, smoothing, embossing, and more in C#. It adds the ability to load and save files, plus a few new effects.
Each of the new effect considers squares areas on the picture. Enter the size you want for the squares in the Rank box and click a button. The new effects are:
Many programmers know that a form's ClientSize property gives the size of the area inside the form's borders but few know that you can set ClientSize. When you set ClientSize, the form resizes itself so it holds its borders and title bar and still has the desired interior size.
When you check the 200x300 or 300x200 radio button, the following event handler executes.
// Change the size. private void rad200x300_CheckedChanged(This code sets the form's ClientSize property to either 200x300 pixels or 300x200 pixels. It then calls the form's Refresh method to raise the Paint event, which is handled by the following event handler.
object sender, EventArgs e) { if (rad200x300.Checked) ClientSize = new Size(200, 300); else ClientSize = new Size(300, 200); Refresh(); }
// Draw a diamond of the appropriate size.
private void Form1_Paint(object sender, PaintEventArgs e)
{
int hgt, wid;
if (rad200x300.Checked) { hgt = 300; wid = 200; }
else { hgt = 200; wid = 300; }
Point[] pts =
{
new Point(wid / 2, 0),
new Point(wid, hgt / 2),
new Point(wid / 2, hgt),
new Point(0, hgt / 2),
};
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
e.Graphics.DrawPolygon(Pens.Blue, pts);
}
This event handler draws a diamond that is either 200x300 or 300x200 to show the form's interior size.
The result is the form resizes itself and then draws a diamond to exactly fit the desired ClientSize.
An indexer property is a property that other code can use inside square brackets to access some value provided by the class, much as you can use the index of an array. Unlike an array, a class's indexer need not be an integer. Furthermore you can overload the indexer and provide more than one indexer for a class.
The following code shows a GradeCollection class. This is really just a wrapper for a Dictionary that holds student names and their grades.
public class GradeCollection
{
// A Dictionary to hold student grades.
private Dictionary Grades = new Dictionary();
// The default indexer property.
// Get or set a student's grade.
public int this[string student]
{
get
{
return Grades[student];
}
set
{
Grades[student] = value;
}
}
// A default indexer property.
// Return a list students with this grade.
public List this[int score]
{
get
{
List students = new List();
foreach (string name in Grades.Keys)
{
if (Grades[name] == score) students.Add(name);
}
return students;
}
}
}
The class contains a Dictionary object to hold the students' names and grades.
The method named this that uses square brackets indicates an indexer. The first indexer takes a string parameter (a student's name) and returns that student's grade. It provides get and set methods that simply delegate their work to the Dictionary object.
The main program uses the following line of code to use this indexer to display a student's grade.
txtGrade.Text = Grades[txtStudent.Text].ToString();The code Grades[txtStudent.Text] uses the indexer to get the grade for the student whose name is in the txtStudent TextBox. The code converts the returned grade into a string and displays it in the txtGrade TextBox. The following code shows how the program uses this indexer to set a student's grade.
Grades[txtStudent.Text] = int.Parse(txtGrade.Text);The left hand side of the statement represents the student's grade. This statement sets the grade equal to the value in the txtGrade TextBox. This class contains a second indexer that takes an integer score as a parameter and returns a collection of students with grades that match that score. The indexer's code loops through the Grades dictionary's keys. If the corresponding student's grade matches the target value, the code adds the student's name to the result list. After it has looped over all of the student names, the indexer returns the list. Note that I don't really recommend that you make such an indexer. It doesn't seem too useful in practice. This example just shows how to make multiple overloaded indexers. Notice also that the second indexer is read-only--you can't use a List as an index to set a bunch of students' scores all at once. (Although writing that piece of the indexer might be a good exercise.)