[C# Helper]
Index Books FAQ Contact About Rod
[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

[C# 24-Hour Trainer]

[C# 5.0 Programmer's Reference]

[MCSD Certification Toolkit (Exam 70-483): Programming in C#]

Title: Draw a heartagram in C#

[Draw a heartagram in C#]

The heartagram is a symbol created by vocalist Ville Valo and used by the band HIM. I'm not particularly a fan, but I think the heartagram shape is interesting so I wanted to make one with a C# program.

The most interesting part of the program is the following MakeHeartagram method, which creates a GraphicsPath that defines the heartagram.

// Return a GraphicsPath representing a heartagram. private GraphicsPath MakeHeartagram(PointF center, float radius, float pen_width) { // Define scales to place the points the right // distances from the edges of the circle. float[] scales = { 1 - 1.75f * pen_width / radius, 1 - 1.75f * pen_width / radius, 0.9f, 0.9f, 1 - 1.75f * pen_width / radius, }; // Find the points of a pentagon within the area. double angle = Math.PI / 2.0; double dtheta = Math.PI * 2.0 / 5.0; PointF[] points = new PointF[5]; for (int i = 0; i < points.Length; i++) { points[i] = new PointF( (float)(center.X + radius * Math.Cos(angle) * scales[i]), (float)(center.Y + radius * Math.Sin(angle) * scales[i])); angle += dtheta; } // Find the top intersection point. PointF top = new PointF( center.X, center.Y - radius * 0.8f); // Build the GraphicsPath. GraphicsPath path = new GraphicsPath(); path.AddLine(points[4], points[1]); float tension = 1f; PointF[] curve1_points = { points[1], points[3], points[0], }; path.AddCurve(curve1_points, tension); PointF[] curve2_points = { points[0], points[2], points[4], }; path.AddCurve(curve2_points, tension); // Close the figure so the last corner is mitered. path.CloseFigure(); return path; }

The method first creates a scales array. The method uses the values in that array to define points arranged in a pentagon. The scale values move the pentagon's points closer or farther from the heartagram's circumference to give a good result.

[Draw a heartagram in C#]

The program uses the variable angle to loop over points that define the pentagon. Each angle is 2 π / 2 radians after the angle before it. As the program loops over the points' angles, it multiplies the heartagram's radius by the angle's scale factor and the sine of cosine of the angle to get the point's location. When the loop is finished, the points array holds the pentagon's points. The picture on the right shows the pentagon with its corners labeled. Notice that the scale values result in a non-centered pentagon. The points must be moved away from the heartagram's circumference to make the final shape's corners just touch the circumference.

The program then creates a GraphicsPath object to hold the heartagram and adds the pieces of the shape to it. It first adds a line from point 4 to point 1.

Next, the code creates a smooth curve starting at point 1, moving to point 3, and ending at point 0. That creates the smooth lobe on the heartagram's upper right.

The code then creates a second smooth curve starting at point 0, moving to point 2, and ending at point 4. That creates the left lobe.

[Draw a heartagram in C#]

The method calls the GraphicsPath object's CloseFigure method to make the figure a closed curve. If you skip that step, the GraphicsPath treats this as an open curve so its other corners are mitered but the final corner is not as shown in the picture on the right.

Finally, the method returns the GraphicsPath object.

The program uses the following method to draw the heartagram's background, enclosing ellipse, and the heartagram itself.

private void DrawHeartagram(Graphics gr, PointF center, float radius, Brush brush, Pen pen) { gr.FillEllipse(brush, center.X - radius, center.Y - radius, 2 * radius, 2 * radius); gr.DrawEllipse(pen, center.X - radius, center.Y - radius, 2 * radius, 2 * radius); // Make a GraphicsPath to represent the heartagram. GraphicsPath path = MakeHeartagram(center, radius, pen.Width); // Draw the GraphicsPath. gr.DrawPath(pen, path); }

This code fills and outlines an ellipse with the brush and pen that are passed into the method. In this example, the brush is a linear gradient brush and the pen is a thick, black pen.

After it draws the ellipse, the program calls the MakeHeartagram method described earlier to get a GraphicsPath that defines the heartagram. The code then draws that path with the pen.

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

The result is pretty good, but it's not exactly the same as the shape drawn by Ville Valo. If you look closely at the heartagram drawn by this program, you'll see that its edges curve in to the shape's points. In other words, they are not straight. You can also see that the rounded lobes in this program's heartagram are narrower than they are in heartagrams that you can find online. Both of those issues arise because this program uses a smooth curve to define the lobes. The heartagrams online seem to use circular arcs for the lobes.

In my next post, I'll describe an improved version of the program that uses circular arcs for the heartagram's lobes.

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

© 2009-2023 Rocky Mountain Computer Consulting, Inc. All rights reserved.