Book Review: Mission Python: Code a Space Adventure Game!

Mission Python: Code a Space Adventure Game!
By Sean McManus
2018, No Starch Press, 280 pages, $20.36

This book explains how you can build a space-themed game in Python. The book’s description implies, but doesn’t actually come out and say, that it is intended for beginners. Although the book is designed for beginners, its eventual goal is still ambitious enough to give even intermediate programmers a challenge. (I’ll say more about that near the end.)

The book explains the basic Python concepts that you need to build the game. For example, it covers lists, variables, strings, loops, functions, and dictionaries.

In addition to explaining how to use Python, the book describes a few useful game-design concepts. For example, it uses collections of images to provide simple animation for things like doors opening and closing, and it uses 20 images to show the astronaut main character walking left, right, up, and down.

In the finished game, you need to move the astronaut through a series of rooms containing bitmapped objects such as walls, chairs, tables, and toilets. You need to avoid obstacles such as closed doors, moving energy balls, and puddles of toxic sludge. On the way, you can pick up useful items such as keys, air canisters, and yo-yos. The graphics are simple and the 3-D effects aren’t very convincing, but the final result is playable and surprisingly fun. Any beginner who works through the book and successfully builds the game has every right to feel proud of that accomplishment!

Overall, I call the book a win, but there are two areas where I think it could have done better.

First, the book does not cover classes. Classes are critical for modern programming and game programming in particular. This book’s game includes several items such as moving energy balls, doors, and toxic puddles that would have made great examples of classes. Instead of using classes, however, the book uses an assortment of lists. For example, large lists contain information about scenery and props by providing indices into an even larger list of objects. It works, but in some ways it feels a bit like FORTAN circa 1980.

My second complaint is that the game may be a bit ambitious for many beginners. The book’s “Age Range” says that it is appropriate for ages 12 and up. I’ve known some 12-year-olds who could work through this book on their own, but I’ve known many others who would have trouble staying focused long enough. The book sometimes requires you to write a fair bit of code without providing immediate results. I know from programming with kids that some kind of immediate results are necessary to keep beginners interested and focused in a project. It also lets beginners know that they are on the right track. Real-world programming often includes stretches without much in the way of results, but it can push a beginner’s attention span to the breaking point, particularly for younger readers.

You can reduce this problem by working in a kid/parent team, tackling the book in limited chunks. You would learn about Python, have a fun game to show off when you’re done, and spend some quality screen time together. Used in that way, the book would be a fun learning experience.

In conclusion, I recommend this book for three audiences: Python beginners with strong attention spans, kid/parent teams, and intermediate Python users who want to tackle a reasonably large, fun, and challenging project. If you don’t fall into one of those categories, then you might be better off starting with a more basic introduction to Python.


Follow me on Twitter   RSS feed   Donate




Posted in books | Tagged , , , , , , , | Leave a comment

New Book: Essential Algorithms, Second Edition: A Practical Approach to Computer Algorithms Using Python and C#

[Book: Essential Algorithms, Second Edition]

I’m happy to announce my latest book, Essential Algorithms, Second Edition: A Practical Approach to Computer Algorithms Using Python and C#.

This is a greatly revised and expanded version of the popular first edition. Here are some links that you can follow to get more information about the book.

Note that this book describes the algorithms using pseudocode, not Python or C#. The text mentions specific Python and C# issues when necessary. The source code for most of the exercises and examples is also available for download in Python and C# versions.

Stop by and take a look. And if you get the book, please post a review when you have a chance!


Follow me on Twitter   RSS feed   Donate




Posted in algorithms, books | Tagged , , , , , , , , | 1 Comment

Make an intuitive extension method to draw an elliptical arc in WPF and C#

[elliptical arc]

In my previous post Draw an elliptical arc in WPF and XAML, I explained how WPF makes you define an elliptical arc. Their method almost makes sense if you want draw an arc as part of a path. however, if you just want to draw a simple elliptical arc, for example to make a pie chart, that method is pretty much useless.

In this post, I’ll explain how you can convert a more natural representation of an elliptical arc into the style that WPF requires.

[example]

The Windows Forms Graphics.DrawArc method takes as parameters a rectangle or a size and position that defines a rectangle. It uses those parameters to define the ellipse. The method also takes parameters that give the arc’s start and end angles. The picture at the right shows the geometry used by that method. Here θ1 and θ2 are the start and end angles.

This method of defining an elliptical arc isn’t perfect. For example, it would be nice if the method told you where the arc’s end points are in case you need to do something with them. But it’s more intuitive than WPF’s approach.

Finding Start and End Points

If you want to use this specification in WPF, you need to know the defining rectangle’s dimensions and where the start and end points are. We already know the rectangle’s dimensions, so we just need to find the end points. To do that, we can use the equations described in my post Calculate where a line segment and an ellipse intersect in C#.

The following method calculates the points where a line segment intersects an ellipse.

// Find the points of intersection between
// an ellipse and a line segment.
private static Point[] FindEllipseSegmentIntersections(
    Rect rect, Point pt1, Point pt2, bool segment_only)
{
    // If the ellipse or line segment are empty, return no intersections.
    if ((rect.Width == 0) || (rect.Height == 0) ||
        ((pt1.X == pt2.X) && (pt1.Y == pt2.Y)))
        return new Point[] { };

    // Make sure the rectangle has non-negative width and height.
    if (rect.Width < 0)
    {
        rect.X = rect.Right;
        rect.Width = -rect.Width;
    }
    if (rect.Height < 0)
    {
        rect.Y = rect.Bottom;
        rect.Height = -rect.Height;
    }

    // Translate so the ellipse is centered at the origin.
    double cx = rect.Left + rect.Width / 2f;
    double cy = rect.Top + rect.Height / 2f;
    rect.X -= cx;
    rect.Y -= cy;
    pt1.X -= cx;
    pt1.Y -= cy;
    pt2.X -= cx;
    pt2.Y -= cy;

    // Get the semimajor and semiminor axes.
    double a = rect.Width / 2;
    double b = rect.Height / 2;

    // Calculate the quadratic parameters.
    double A = (pt2.X - pt1.X) * (pt2.X - pt1.X) / a / a +
               (pt2.Y - pt1.Y) * (pt2.Y - pt1.Y) / b / b;
    double B = 2 * pt1.X * (pt2.X - pt1.X) / a / a +
               2 * pt1.Y * (pt2.Y - pt1.Y) / b / b;
    double C = pt1.X * pt1.X / a / a + pt1.Y * pt1.Y / b / b - 1;

    // Make a list of t values.
    List<double> t_values = new List<double>();

    // Calculate the discriminant.
    double discriminant = B * B - 4 * A * C;
    if (discriminant == 0)
    {
        // One real solution.
        t_values.Add(-B / 2 / A);
    }
    else if (discriminant > 0)
    {
        // Two real solutions.
        t_values.Add((double)((-B + Math.Sqrt(discriminant)) / 2 / A));
        t_values.Add((double)((-B - Math.Sqrt(discriminant)) / 2 / A));
    }

    // Convert the t values into points.
    List<Point> points = new List<Point>();
    foreach (double t in t_values)
    {
        // If the points are on the segment (or we
        // don't care if they are), add them to the list.
        if (!segment_only || ((t >= 0f) && (t <= 1f)))
        {
            double x = pt1.X + (pt2.X - pt1.X) * t + cx;
            double y = pt1.Y + (pt2.Y - pt1.Y) * t + cy;
            points.Add(new Point(x, y));
        }
    }

    // Return the points.
    return points.ToArray();
}

See the earlier post for an explanation of how this method works. For this post, we really just need to know how to use it.

The rect parameter gives the rectangle that defines the ellipse. The pt1 and pt2 parameters are the line segment’s end points.

The segment_only parameter indicates whether the method should only return points of intersection that lie on the line segment, or whether it should also return points that lie in the segment’s extension. This example will use a segment that starts at the ellipse’s center and extends beyond the ellipse’s edge. We’ll set the segment_only parameter to true so the method only returns the point of intersection and not the intersection that you get if you extend the segment in the opposite direction.

The following method uses the FindEllipseSegmentIntersections method to find the elliptical arc’s start and end points.

// Find the points of on an ellipse
// at the indicated angles from is center.
private static Point[] FindEllipsePoints(
    Rect rect, double angle1, double angle2)
{
    // Find the ellipse's center.
    Point center = new Point(
        rect.X + rect.Width / 2.0,
        rect.Y + rect.Height / 2.0);

    // Find segments from the center in the
    // desired directions and long enough to
    // cut the ellipse.
    double dist = rect.Width + rect.Height;
    Point pt1 = new Point(
        center.X + dist * Math.Cos(angle1),
        center.Y + dist * Math.Sin(angle1));
    Point pt2 = new Point(
        center.X + dist * Math.Cos(angle2),
        center.Y + dist * Math.Sin(angle2));

    // Find the points of intersection.
    Point[] intersections1 =
        FindEllipseSegmentIntersections(
            rect, center, pt1, true);
    Point[] intersections2 =
        FindEllipseSegmentIntersections(
            rect, center, pt2, true);
    return new Point[]
    {
        intersections1[0],
        intersections2[0]
    };
}

This method finds the ellipse’s center. It then uses the angles’ sines and cosines to find a point along the lines starting at the center and pointing in the directions of the start and end angles. The segment’s length is the ellipse’s width plus its height, so the segment is long enough to intersect with the ellipse.

The method then calls FindEllipseSegmentIntersections to see where the segments intersect the ellipse. The method returns the two points of intersection in an array.

Drawing the Elliptical Arc

The following extension method uses the WPF style specification to draw an elliptical arc.

// Add an Arc to a Canvas.
public static Path DrawArc(this Canvas canvas,
    Brush fill, Brush stroke, double stroke_thickness,
    Point start_point, Point end_point, Size size,
    double rotation_angle, bool is_large_arc,
    SweepDirection sweep_direction, bool is_stroked)
{
    // Create a Path to hold the geometry.
    Path path = new Path();
    canvas.Children.Add(path);
    path.Fill = fill;
    path.Stroke = stroke;
    path.StrokeThickness = stroke_thickness;

    // Add a PathGeometry.
    PathGeometry path_geometry = new PathGeometry();
    path.Data = path_geometry;

    // Create a PathFigure.
    PathFigure path_figure = new PathFigure();
    path_geometry.Figures.Add(path_figure);

    // Start at the first point.
    path_figure.StartPoint = start_point;

    // Create a PathSegmentCollection.
    PathSegmentCollection path_segment_collection =
        new PathSegmentCollection();
    path_figure.Segments = path_segment_collection;

    // Create the ArcSegment.
    ArcSegment arc_segment = new ArcSegment(
        end_point, size, rotation_angle,
        is_large_arc, sweep_direction, is_stroked);
    path_segment_collection.Add(arc_segment);

    return path;
}

This method creates a Path and sets its drawing properties. It then makes a PathGeometry object and adds it to the Path object’s Figures collection. It sets the PathFigure object’s StartPoint property to the arc’s starting point.

Next, the method sets the PathGeometry object’s Segments property equal to a new PathSegmentCollection. Finally, the code creates a new ArcSegment and adds it to the PathSegmentCollection.

The following code shows an extension method that uses the new Graphics.DrawArc style for specifying the elliptical arc.

// Draw an elliptical arc. Return the end points.
public static Path DrawArc(this Canvas canvas,
    Brush fill, Brush stroke, double stroke_thickness,
    Rect rect, double angle1, double angle2,
    bool is_large_arc, SweepDirection sweep_direction,
    out Point point1, out Point point2)
{
    Point[] points = FindEllipsePoints(
        rect, angle1, angle2);
    point1 = points[0];
    point2 = points[1];

    Size size = new Size(rect.Width / 2, rect.Height / 2);
    return canvas.DrawArc(
        fill, stroke, stroke_thickness,
        points[0], points[1], size, 0, is_large_arc,
        sweep_direction, true);
}

This version takes as parameters a rectangle that defines the ellipse’s size and position, and the arc’s start and end angles. It passes those values to the FindEllipsePoints method. It then passes the returned start and end points into the previous DrawArc extension method to draw the arc.

You still need to figure out whether the arc should be large and whether it should run clockwise or counterclockwise, but that’s a lot easier than calculating where the arc’s endpoints should be.

Summary

[example]

There are some special cases where WPF’s method for specifying an elliptical arc is almost manageable. For example, if you want to connect a vertical line segment with a horizontal segment as shown in the picture on the right, then you can probably figure out how to tell WPF what arc to draw. If you just want to draw a simple arc, however, the second DrawArc extension described here will probably be a lot easier.

In my next post, I’ll show how you can modify the DrawArc extension method to draw pie slices.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in drawing, geometry, graphics, mathematics, wpf, XAML | Tagged , , , , , , , , , , , , | 2 Comments

Draw an elliptical arc in WPF and XAML

[elliptical arc]

This example shows how to use XAML code to draw an elliptical arc in a WPF. The following sections explain how to specify an elliptical arc and how to draw one in XAML.

Specifying an Elliptical Arc

WPF provides several drawing classes that draw shapes such as Rectangle, Line, and Ellipse. Those objects are relatively straightforward. You create the object and then specify some fairly intuitive properties to determine the shape’s geometry, fill, and outline characteristics.

Unfortunately, the ArcSegment class that WPF uses to draw an elliptical arc provides a nice demonstration of the unofficial WPF slogan, “Twice as flexible and only five times as hard.” Actually drawing an elliptical arc may be ten times as hard as it is in Windows Forms. Here’s what I think the WPF engineers were thinking and how you draw arcs in WPF.

What Were They Thinking?

Shapes such as rectangles and ellipses are two-dimensional closed shapes. An elliptical arc is a one-dimensional curve. Because it is a curve, you might want to use it in a sequence of other one-dimensional shapes such as Bézier curves, polylines, and lines. (The model is inconsistent here because you can add lines to a sequence but there is also a perfectly usable Line class.)

If you are going to use an elliptical arc in a sequence of curves, it might make sense to specify the arc by giving its start and end points. Then, for example, you could draw a line from point A to point B, a Bézier curve from point B to point C, an elliptical arc from point C to point D, and so forth.

In general there are an infinite number of ellipses that pass through two given points, so you need to specify a few other parameters to determine the ellipse to use and the arc on that ellipse that you want. For some reason, the WPF developers decided that you should specify the width and height of the ellipse that defines the arc. In general, there are up to two ellipses with a given width and height that pass through two points. The picture at the top of this post shows two black points and two ellipses (pink and blue) with a specific size that pass through the points.

If you look at the ellipses in the figure, you can see that there are four elliptical arcs that move from the blue point to the red point. The pink and blue ellipses each contain two of the arcs, one in the clockwise direction and one in the counterclockwise direction.

Notice that the larger arc from the blue point to the red point is in the clockwise direction on the blue ellipse and in the counterclockwise direction on the pink ellipse.

The parameters that you need to specify to completely determine the elliptical arc are the following.

  • The start point
  • The end point
  • The width and height of the ellipse
  • Whether the elliptical arc is a large arc (spans more than half of the ellipse)
  • Whether the arc should move clockwise or counterclockwise

Drawing an Elliptical Arc

Unfortunately, you can’t just place an elliptical arc inside another control such as a Grid or Canvas in WPF. Because WPF assumes that the arc is part of a sequence of one-dimensional curves, you need to build such a sequence even if you only want to draw a single arc.

To build the arc, you need to create a Path. Then you set the Path object’s Data property to a PathGeometry object. You then give the PathGeometry object a new PathFigure object.

The PathFigure object’s StartPoint property indicates where the sequence of one-dimensional shapes begins. If you only want to draw a single elliptical arc, then this is its start point. If you are drawing a sequence of shapes, then each shape’s start point is where the previous shape ended.

The PathFigure object contains a segment collection. This is where you finally add an ArcSegment to represent the elliptical arc.

Here’s the XAML code that the example program uses to create the blue dashed arc shown in the picture at the top of the post.

<Path Stroke="Blue" StrokeThickness="3" StrokeDashArray="5,5">
    <Path.Data>
        <PathGeometry>
            <PathGeometry.Figures>
                <PathFigureCollection>
                    <PathFigure StartPoint="62,114">
                        <PathFigure.Segments>
                            <PathSegmentCollection>
                                <ArcSegment Size="90,70"
                                    RotationAngle="0"
                                    IsLargeArc="True"
                                    SweepDirection="Clockwise"
                                    Point="198,159" />
                            </PathSegmentCollection>
                        </PathFigure.Segments>
                    </PathFigure>
                </PathFigureCollection>
            </PathGeometry.Figures>
        </PathGeometry>
    </Path.Data>
</Path>

Simple, isn’t it?

Notice how the PathFigure determines the arc’s starting point. If you look again at the picture, you should be able to see how the IsLargeArc and SweepDirection parameters determine which arc to draw.

Personally, I find this a ridiculously difficult way to specify an elliptical arc. In my next post, I’ll explain how you can specify an arc in a more intuitive way. (The way that’s used by Windows Forms programs and every other drawing package I’ve used.)


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in drawing, geometry, graphics, mathematics, wpf, XAML | Tagged , , , , , , , , , , , , | Leave a comment

Make drawing extension methods in WPF and C#

[example]

This example shows how to make a few handy extension methods for drawing in WPF. Drawing in WPF is at best awkward and at worst downright painful.


For example, the following XAML code draws a rectangle.

<Grid>
    <Canvas Name="canDrawing">
        <Rectangle Canvas.Left="100" Canvas.Top="30"
            Width="100" Height="70" Fill="LightGreen"
            Stroke="Green" StrokeThickness="5"/>
    </Canvas>
</Grid>

This is awkward but survivable. The following code shows how you can use C# code to draw the same rectangle.

Rectangle rectangle = new Rectangle();
Canvas.SetLeft(rectangle, 100);
Canvas.SetTop(rectangle, 30);
rectangle.Width = 100;
rectangle.Height = 70;
rectangle.Fill = Brushes.LightGreen;
rectangle.Stroke = Brushes.Green;
rectangle.StrokeThickness = 5;
canDrawing.Children.Add(rectangle);

This code creates a new Rectangle object. The Left and Top properties are attached properties provided by the Canvas class, not properties of the rectangle. That means you cannot simply set the properties for the rectangle. Instead you must use the Canvas class’s SetLeft and SetTop methods to set those values. (I get why they did it this way. The Left and Top properties only make sense if the rectangle is inside some other control, but why didn’t they just give shape objects their own properties?)

The code then sets the rectangle’s Width, Height, Fill, Stroke, and StrokeThickness properties. Finally, the program adds the rectangle to the Canvas control’s child list.

All of this is manageable, but awkward. It would be nice if the Canvas class provided a method for creating a Rectangle object and setting at least some of its properties.

The following code shows an extension method that does that.

public static class DrawingExtensions
{
    // Add a Rectangle to a Canvas.
    public static Rectangle DrawRectangle(
        this Canvas canvas, Brush fill, Brush stroke,
        double stroke_thickness, double left,
        double top, double width, double height)
    {
        Rectangle rectangle = new Rectangle();
        Canvas.SetLeft(rectangle, left);
        Canvas.SetTop(rectangle, top);
        rectangle.Width = width;
        rectangle.Height = height;
        rectangle.Fill = fill;
        rectangle.Stroke = stroke;
        rectangle.StrokeThickness = stroke_thickness;
        canvas.Children.Add(rectangle);
        return rectangle;
    }
}

Recall that extension methods must be contained in public static classes. that’s why this method is inside the DrawingExtensions class.

The method must also be static. Its first parameter is marked with the this keyword to indicate that the method extends that object. In this case, the method extends the Canvas class.

The body of the method simply performs the same steps shown earlier that use C# code to create a new Rectangle object, set its parameters, and add it to the Canvas control’s Children collection.

The method finishes by returning the new Rectangle object in case the calling code needs to manipulate it. For example, that code could change the object’s properties or set other properties.

The main program can use this method as in the following.

rect = new Rect(50, 80, 200, 20);
rectangle = canDrawing.DrawRectangle(Brushes.Pink,
    Brushes.Red, 5, 100, 30, 100, 70);
rectangle.RadiusX = 10;
rectangle.RadiusY = 10;

This code uses the extension method to create the Rectangle. It then changes the returned Rectangle object’s RadiusX and RadiusY properties to give the rectangle rounded corners.

Sometimes it may be convenient to define a Rectangle by giving a Rect that defines its size and position. The following code shows an extension method

public static Rectangle DrawRectangle(
    this Canvas canvas, Brush fill, Brush stroke,
    double stroke_thickness, Rect rect)
{
    return canvas.DrawRectangle(fill, stroke,
        stroke_thickness, rect.Left, rect.Top,
        rect.Width, rect.Height);
}

This method simply invokes the previous version of the extension method, using the Rect parameter’s properties to fill in the rectangle’s size and position.

The example program also defines extension methods to draw ellipses. Those methods are so similar to the DrawRectangle extension methods that I won’t show them here. Download the example to see the details.

The following code shows the DrawLine extension method.

public static Line DrawLine(
    this Canvas canvas, Brush stroke,
    double stroke_thickness, double x1, double y1,
    double x2, double y2)
{
    Line line = new Line();
    line.X1 = x1;
    line.Y1 = y1;
    line.X2 = x2;
    line.Y2 = y2;
    line.Stroke = stroke;
    line.StrokeThickness = stroke_thickness;
    canvas.Children.Add(line);
    return line;
}

This method creates a Line object and sets its X1, Y1, X2, and Y2 properties. It sets the stroke properties and returns the new object.

The following code shows the DrawPolyline extension method.

public static Polyline DrawPolyline(
    this Canvas canvas, Brush stroke,
    double stroke_thickness,
    IEnumerable<Point> points)
{
    Polyline polyline = new Polyline();
    polyline.Points = new PointCollection(points);
    polyline.Stroke = stroke;
    polyline.StrokeThickness = stroke_thickness;
    canvas.Children.Add(polyline);
    return polyline;
}

This method is similar to the others. It creates the new drawing object, sets its properties, and returns the new object. This method is a bit different because it needs to define object’s Points property. That property must be set to a PointCollection object. Fortunately that object’s constructor can take an IEnumerable<Point> as a parameter so initializing the points is easy.

Most of the example’s main program is simple because it uses these extension methods. For example, the following code shows how the program makes the face’s left eye and pupil.

// Left eye.
rect = new Rect(100, 110, 40, 50);
canDrawing.DrawEllipse(Brushes.White,
    Brushes.Black, 5, rect);

// Left pupil.
rect = new Rect(120, 120, 20, 30);
canDrawing.DrawEllipse(Brushes.Black,
    Brushes.Black, 5, rect);

Download the example to see the rest of the code. In particular, the program uses a loop to define the points that make up the mustache’s polyline.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in drawing, extension methods, graphics, wpf, XAML | Tagged , , , , , , , , , | 2 Comments

Make an image containing shadowed text in WPF and C#


[shadowed text]

Recently I wanted an image containing shadowed text similar to the following to put on my new Favorite Books page. It’s fairly easy to make this kind of text in Microsoft Word and then save an image of it in a file, but I wanted the result to have a transparent background. I could have converted white pixels into transparent ones, but that can cause some problems as described in my post Use transparency when drawing with anti-aliasing in C#.

So I decided to write this program.

This would have been relatively straightforward in a Windows Forms program except for the most important part: drawing the shadowed text. WPF has a DropShadowBitmapEffect class that makes this easy. For some reason, Microsoft has marked that class as obsolete, but until they completely remove it, I’m going to use it!

True to its unofficial slogan, “Twice as flexible and only five times as hard,” the WPF program took a lot longer than it should have, but the result is pretty useful. Here are the main hurdles.

Font Weight

The program’s XAML code is mostly straightforward. The window contains a Grid with eight rows and two columns. The first three rows simply contain labels and text boxes. The next three rows are a bit more interesting.

Ideally the font weight row should contain the allowed font weights: thin, extra-light, light, normal, medium, semi-bold, bold, extra-bold, black, and extra-black. However, when the code sets the display label’s FontWeight property, it must convert those textual values into a proper font weight. The weights are defined to have the numeric values 100, 200, …, 900, and 950 (for extra-black), but even that’s what you need. The FontWeight property must take a FontWeight value that is defined by a property of the FontWeights class. That means you can’t simply set the property equal to the textual value or the numeric value.

The solution is actually fairly simple after you figure it out. When the program loads, it uses the following code to initialize the font weight combo box.

// Build the Font Weight combo box.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
    // Build the weight combo box.
    cboWeight.Items.Add(FontWeights.Thin);
    cboWeight.Items.Add(FontWeights.ExtraLight);
    cboWeight.Items.Add(FontWeights.Light);
    cboWeight.Items.Add(FontWeights.Regular);
    cboWeight.Items.Add(FontWeights.Medium);
    cboWeight.Items.Add(FontWeights.SemiBold);
    cboWeight.Items.Add(FontWeights.Bold);
    cboWeight.Items.Add(FontWeights.ExtraBold);
    cboWeight.Items.Add(FontWeights.Black);
    cboWeight.Items.Add(FontWeights.ExtraBlack);
    cboWeight.SelectedIndex = 3;
}

This code adds the values defined by the FontWeights class to the combo box. Those values’ ToString methods return their textual values, so that is what the combo box displays. Later, when it needs to build the sample font, the code uses the following statement to convert the current combo box selection into the correct FontWeight value.

When the user selects an item in the combo box, the following code executes.

lblResult.FontWeight = (FontWeight)cboWeight.SelectedItem;

Colors

In a Windows Forms application, it would be easy to display a color sample. If the user clicked the sample, the program could display a color dialog to let the user pick any color.

Unfortunately, WPF does not include a color selection dialog. You could build one (a lot of work to do properly) or include enough of the Windows Forms libraries to let you use its dialog. I decided to display sample colors that you can pick.

The following XAML code shows how the program defines the text color samples.

<StackPanel Grid.Row="4" Grid.Column="1" Orientation="Horizontal">
    <StackPanel.Resources>
        <Style TargetType="Canvas">
            <Setter Property="Margin" Value="0,0,3,0"/>
        </Style>
    </StackPanel.Resources>
    <Canvas Width="20" Height="20" Background="Black" MouseDown="TextColor_MouseDown"/>
    <Canvas Width="20" Height="20" Background="White" MouseDown="TextColor_MouseDown"/>
    <Canvas Width="20" Height="20" Background="Green" MouseDown="TextColor_MouseDown"/>
    <Canvas Width="20" Height="20" Background="Blue" MouseDown="TextColor_MouseDown"/>
    <Canvas Width="20" Height="20" Background="Red" MouseDown="TextColor_MouseDown"/>
    <Canvas Width="20" Height="20" Background="Yellow" MouseDown="TextColor_MouseDown"/>
    <Canvas Width="20" Height="20" Background="Orange" MouseDown="TextColor_MouseDown"/>
</StackPanel>

This code starts with a horizontal StackPanel. Its resources add a margin around any Canvas control contained in the StackPanel. (That way you don’t need to add a margin around each sample individually.)

[shadowed text]

The StackPanel contains a sequence of Canvas objects, each having a different Background property. (Note that the second Canvas is white so it’s invisible on the white background used by the program. When you run the program, you need to know it’s there if you want to use white text or a white shadow.)

When you click any of these Canvas controls, the following code executes.

// The text and shadow colors.
private SolidColorBrush TextBrush = Brushes.Black;
private SolidColorBrush ShadowBrush = Brushes.Black;

private void TextColor_MouseDown(object sender, MouseButtonEventArgs e)
{
    Canvas canvas = sender as Canvas;
    TextBrush = (SolidColorBrush)canvas.Background;
    ShowText();
}

This code first defines two form-level SolidColorBrush objects to record the colors used by the shadowed text and its shadow. In the TextColor_MouseDown event handler, we know that the control that raised the event is a Canvas control, so the code converts the sender parameter into a Canvas.

When you set a control’s Background property to a color in XAML code, you are actually setting the property to a SolidColorBrush that displays that color. The event handler simply saves the Background property in the TextBrush variable for later use. However, the Background property has the type Brush (it could hold other kinds of brushes such as gradient brushes or image brushes), so the code casts it into a SolidColorBrush before saving it in the TextBrush variable.

The code that manages the shadow color samples is similar.

Display the Sample

Whenever the user changes one of the sample text parameters, the program calls the following ShowText method to display a sample of the text.

// Display the sample text.
private void ShowText()
{
    if (!this.IsLoaded) return;

    try
    {
        lblResult.Content = txtText.Text;
        lblResult.FontFamily = new FontFamily(txtFont.Text);
        lblResult.FontSize = int.Parse(txtFontSize.Text);
        lblResult.FontWeight = (FontWeight)cboWeight.SelectedItem;
        lblResult.Foreground = TextBrush;

        DropShadowBitmapEffect effect =
            lblResult.BitmapEffect as DropShadowBitmapEffect;
        effect.Color = ShadowBrush.Color;
    }
    catch
    {
    }
}

The method first checks whether the window has finished loading and exits if it has not. This is important because the controls’ event handlers execute when the window first loads and sets their values. For example, when the program sets the initial value for the topmost text box, it triggers that control’s TextChanged event and the event handler calls the ShowText method. The method then tries to access the values of all of the controls, and many of them do not yet exist. When the program tries to access the font name, size, or weight, the corresponding control is null so the program crashes. The sample label also doesn’t exist yet, so if the program tries to display a sample, it crashes.

After it has verified that the window is loaded so all of its controls exist, the ShowText method sets properties for the lblSample Label control.

The method’s final steps create a DropShadowBitmapEffect object and set its Color property. That property is of the Color data type, so the code gets the color from the ShadowBrush variable.

Saving the Result

When you click the Save Image button, the following code executes.

// Save the grdText control's image.
private void btnSave_Click(object sender, RoutedEventArgs e)
{
    SaveFileDialog dlg = new SaveFileDialog();
    dlg.FileName = txtText.Text;        // Default name.
    dlg.DefaultExt = ".png";            // Default extension.
    dlg.Filter = "PNG Files|*.png|All files|*.*";
    dlg.FilterIndex = 0;
    
    // Display the dialog.
    if (dlg.ShowDialog() == true)
    {
        SaveControlImage(grdText, dlg.FileName);
    }
}

This code creates a SaveFileDialog object and sets its FileName to the sample text. It sets the default extension to .png, sets the filter to search for .png files or all files, and selects the first filter. (The one that searches for .png files.) The program does not bother with other kinds of files such as .jpg or .gif files because they don’t support transparency.

The program then displays the dialog. If the user picks a file and clicks Save, the program calls the SaveControlImage method passing it the Grid control grdText, which contains the sample label.

Notice that the code explicitly compares the result of the dialog’s ShowDialog method to the value true. It cannot simply say, if (dlg.ShowDialog()) because that method returns Nullable<bool> instead of a simple bool. I don’t know why they made it return a nullable value because it always returns either true or false, never null, but there you are.

The following code shows how the program defines the grdText control and the sample label that it contains.

<Grid Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2"
    Background="Transparent" Name="grdText" >
    <Label HorizontalAlignment="Center" VerticalAlignment="Center"
        FontSize="50" FontFamily="Brush Script MT"
        Name="lblResult" Content="Favorite Books">
            <Label.BitmapEffect>
                <DropShadowBitmapEffect Color="Black"
                Direction="-45" ShadowDepth="10" Softness=".7"/>
            </Label.BitmapEffect>
    </Label>
</Grid>

This code defines the grid and sample label. The most important non-obvious thing to notice is that the grid’s Background property is set to Transparent. When the SaveControlImage method saves the grid’s image, it saves the transparent background.

Speaking of the SaveControlImage method, you can read more about it in my post Save WPF control images in C#.

Summary

When all’s said and done, the program isn’t particularly long. It’s getting there that’s the challenge.

Download the example to experiment with it and see additional details. Go to my post Save WPF control images in C# to learn more about the SaveControlImage method.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in graphics, image processing | Tagged , , , , , , , , | Leave a comment

“WPF 3d” is now available for Kindle

[WPF 3d]

My book WPF 3d: Three-Dimensional Graphics with WPF and C# is now available in the Kindle Store. I’m not completely thrilled with the way it was converted for Kindle (there are some formatting errors), but it should be usable.

Here are some related links:



Download Example   Follow me on Twitter   RSS feed   Donate




Posted in 3D, 3D graphics, books, drawing, graphics, three-dimensional graphics | Tagged , , , , , , , , , , | 4 Comments

New page: Favorite Books


[favorite books]

I’ve started a favorite books page. Go there if you are curious to see the kinds of books I like to read for fun. This is only a small sample of my favorites, but it should give you some suggestions.

The initial version of the page lists books by the following authors: Tom Holt, Jasper Fforde, Jason Sheehan, Christopher Moore, Ernest Cline, A. Lee Martinez, and Terry Pratchett. I’ll add a few more when I have time.

Feel free to post your own recommendations in that page’s comments.


Follow me on Twitter   RSS feed   Donate




Posted in books | Tagged , | Leave a comment

Use transparency when drawing with anti-aliasing in C#

[anti-aliasing]

Anti-aliasing makes drawn lines look smoother on a raster image such as a bitmap or on the screen. Transparency allows you to place an image on top of another image so parts of the underlying image show through. Unfortunately, when you use both anti-aliasing and transparency, you can get some odd results.

To see where the problem occurs, consider the picture at the top of this post. To make the drawing on the left, the program drew a smiley face in a bitmap with a white background. It then used the bitmap’s MakeTransparent method to make the image’s white pixels transparent. It finished by copying the bitmap, which now has some transparent pixels, onto a PictureBox that has a blue background.

The problem is that the smiley face was drawn with anti-aliasing. The result is that the pixels at the border between two colors are blended. For example, where the smiley’s black outline pixels meet the white background, some pixels are converted into shades of gray to make them blend more smoothly together. Then when the program calls MakeTransparent, that method only makes the pixels that are pure white into transparent pixels. The gray ones remain gray. When the program draws the result on the blue background, you can still see the gray pixels along the edge of the smiley face. (Look closely at the edges of the left smiley in the picture above.)

Approaches

There are a couple of ways that you can fix this problem. First, you can ensure that the background in the original image matches (at least approximately) the background in the final image on which you display the bitmap. Then the anti-aliased pixels blend properly. In this example, the program could either use a blue background for the original image before calling MakeTransparent, or it could use a white background in the final image instead of a blue one.

That solution only works if you have control over how the original image or the final image is produced. If you are simply given an image with transparent pixels and you need to place it over a specific background image, then you might not have that luxury.

A second approach would be to examine all of the transparent bitmap’s pixels and find the ones that use anti-aliasing. In this example, you would look for shades of gray. You would then convert the background component into a transparent component. For example, if a gray pixel is 50% white and 50% black, you would convert it into 50% transparent and 50% black. This approach should work well, but only if you know which colors show the anti-aliasing. It also requires you to examine every pixel in the the image, which could be slow.

A third approach, and by far the best, is to draw the original image on a transparent background. Then the anti-aliasing will make the appropriate pixels semi-transparent as needed just as if you had taken the second approach. Now no matter what background you place behind the bitmap, the transparent parts of the image will blend perfectly.

The program uses this third approach to draw the right smiley in the picture at the top of this post. If you look closely, you’ll see that the smiley’s edge blend smoothly with the background.

[anti-aliasing]

The picture on the right shows a closeup of the area between the smileys. In this enlarged image, you can easily see the pixels that provide the anti-aliasing. In the left smiley, they are shades of gray that blended the original white background with the smiley’s black edge. In the right smiley, those pixels have colors that include some transparency so they blend the smiley’s black edge with any background color (in this case, blue). (You can also see in both smileys how the black and yellow pixels blend together.)

Code

The following code shows how the program draws its two smiley bitmaps, each of which contains some transparent background pixels.

private Bitmap FgOnWhite, FgOnTransparent;

// Draw the smiley bitmaps with transparent backgrounds.
private void Form1_Load(object sender, EventArgs e)
{
    int wid = pictureBox1.ClientSize.Width;
    int hgt = pictureBox1.ClientSize.Height;

    // Draw with a white background.
    FgOnWhite = new Bitmap(wid, hgt);
    using (Graphics gr = Graphics.FromImage(FgOnWhite))
    {
        gr.SmoothingMode = SmoothingMode.AntiAlias;
        gr.Clear(Color.White);
        DrawSmiley(gr, pictureBox1.ClientRectangle, 10);

        // Make the white pixels transparent.
        FgOnWhite.MakeTransparent(Color.White);
    }

    // Draw with a transparent background.
    FgOnTransparent = new Bitmap(wid, hgt);
    using (Graphics gr = Graphics.FromImage(FgOnTransparent))
    {
        gr.SmoothingMode = SmoothingMode.AntiAlias;
        gr.Clear(Color.Transparent);
        DrawSmiley(gr, pictureBox1.ClientRectangle, 10);
    }
}

The code declares the FgOnWhite and FgOnTransparent bitmaps outside of any method so all of the form’s code can use them.

The form’s Load event handler first creates the FgOnWhite bitmap. It clears the bitmap with white and then calls the DrawSmiley method described shortly to draw on it. It then calls the bitmap’s MakeTransparent method to make the white pixels transparent.

Next, the program repeats roughly the same steps to make another smiley bitmap. This time it clears the bitmap with the color Transparent before it draws. Because the background is already transparent, it doesn’t need to call MakeTransparent for this bitmap.

The following method draws a smiley face in a given rectangle.

// Draw a smiley face in the rectangle.
private void DrawSmiley(Graphics gr,
    Rectangle rect, int wid)
{
    rect.Inflate(-4, -4);
    using (Pen pen = new Pen(Color.Black, wid))
    {
        // Face.
        Rectangle r = new Rectangle(
            rect.Left + wid / 2, rect.Top + wid / 2,
            rect.Width - wid, rect.Height - wid);
        gr.FillEllipse(Brushes.Yellow, r);
        gr.DrawEllipse(pen, r);

        // Smile.
        pen.Width /= 2;
        r.Inflate(-30, -30);
        gr.DrawArc(pen, r, 10, 160);

        // Left eye.
        int eye_wid = (int)(rect.Width * 0.2);
        int eye_hgt = (int)(rect.Height * 0.25);
        Rectangle eye_r = new Rectangle(
            (int)(rect.Left + rect.Width * 0.25),
            (int)(rect.Top + rect.Height * 0.20),
            eye_wid, eye_hgt);
        gr.FillEllipse(Brushes.LightBlue, eye_r);
        gr.DrawEllipse(pen, eye_r);
        Rectangle pupil_r = new Rectangle(
            eye_r.Left + eye_wid / 2,
            eye_r.Top + eye_hgt / 4,
            eye_wid / 2, eye_hgt / 2);
        gr.FillEllipse(Brushes.Black, pupil_r);
        gr.DrawEllipse(pen, pupil_r);

        // Right eye.
        eye_r = new Rectangle(
            (int)(rect.Right - rect.Width * 0.25) - eye_wid,
            (int)(rect.Top + rect.Height * 0.20),
            eye_wid, eye_hgt);
        gr.FillEllipse(Brushes.LightBlue, eye_r);
        gr.DrawEllipse(pen, eye_r);
        pupil_r = new Rectangle(
            eye_r.Left + eye_wid / 2,
            eye_r.Top + eye_hgt / 4,
            eye_wid / 2, eye_hgt / 2);
        gr.FillEllipse(Brushes.Black, pupil_r);
        gr.DrawEllipse(pen, pupil_r);

        // Nose.
        Rectangle nose_r = new Rectangle(
            (int)(rect.Left + rect.Width / 2) - eye_wid / 2,
            (int)(rect.Top + rect.Height * 0.45),
            eye_wid, eye_wid);
        gr.FillEllipse(Brushes.LightGreen, nose_r);
        gr.DrawEllipse(pen, nose_r);
    }
}

This method simply draws the smiley’s outline, smile, eyes, and nose.

The last pieces of the program are the following event handlers, which draw the smiley bitmaps on top of blue backgrounds when each of the form’s PictureBox controls raises its Paint event.

// Draw the smiley face on the control.
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.Clear(Color.Blue);
    e.Graphics.DrawImage(FgOnWhite, 0, 0);
}

private void pictureBox2_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.Clear(Color.Blue);
    e.Graphics.DrawImage(FgOnTransparent, 0, 0);
}

These event handlers simply clear their controls with blue and then draw the smiley bitmaps on top of them.

Download the example to experiment with it. For example, instead of clearing the controls with blue, give them a BackgroundImage at design time and then just draw the smiley images over them in their Paint event handlers.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in drawing, graphics, image processing | Tagged , , , , , , , , | 1 Comment

Graph equations while preserving aspect ratio in WPF and C#


[graph]

The previous example, Graph equations entered by the user in WPF and C# shows how to graph an equation entered by the user. The user must also enter the coordinate bounds that the program should use. For example, in the following picture, the program is graphing the equation for -20 < X < 20, -3 < Y < 12.


[graph]

Unfortunately the coordinates entered by the user compress the graph horizontally. To understand why, you need to know a few simple terms.

The world window is the area in world coordinates where the equation is evaluated. It’s the X and Y coordinates that we want to draw. The units of the world window are whatever wee find convenient. The user enters the bounds for the world window.

The device window is the area on the screen where we want to draw the graph. Those units are pixels and the size of the device window is determined by the size of the Canvas control where we draw the graph.

Finally, an aspect ratio is the ratio of a window’s width to height.

The reason the graph in the previous picture is compressed is that the world window’s aspect ratio is not the same as the device window’s aspect ratio. In this example, the world window is relatively short and wide, so it must be compressed horizontally to fit in the device window.

One way you can avoid this problem is to adjust the world window so it has the same aspect ratio as the device window. (You cannot adjust the device window without resizing the Canvas control, so our only hope is to adjust the world window.)

This example does uses the following AdjustAspectRatio method to give the world window the same aspect ratio as the device window.

// Make the world and device aspect ratios match.
private void AdjustAspectRatio(
    ref double wxmin, ref double wxmax,
    ref double wymin, ref double wymax,
    double dxmin, double dxmax, double dymin, double dymax)
{
    double wwid = wxmax - wxmin;
    double whgt = wymax - wymin;
    double dwid = dxmax - dxmin;
    double dhgt = dymax - dymin;
    double w_aspect = wwid / whgt;
    double d_aspect = Math.Abs(dwid / dhgt);
    if (w_aspect > d_aspect)
    {
        // The world window is too short
        // and wide. Make it taller.
        whgt = wwid / d_aspect;

        double wcy = (wymax + wymin) / 2;
        wymin = wcy - whgt / 2;
        wymax = wcy + whgt / 2;
    }
    else
    {
        // The world window is too tall
        // and thin. Make it wider.
        wwid = whgt * d_aspect;

        double wcx = (wxmax + wxmin) / 2;
        wxmin = wcx - wwid / 2;
        wxmax = wcx + wwid / 2;
    }
}

This method calculates the widths and heights of the world and device windows. It then uses them to calculate the windows’ aspect ratios.

Notice that the code takes the absolute value of the aspect ratio for the device window. It does that because device coordinates usually start with (0, 0) in the upper left corner and increase downward and to the right. That means ymin may be larger than ymax. That will give a negative device window width and therefore a negative aspect ratio.

After it has calculated the aspect ratios, the code compares them. If the world aspect ratio is greater than the device aspect ratio, then the world window is too short and wide compared to the device window. In that case, the code calculates the height is needs to give the world window to make it have the same aspect ratio as the device window. It updates wxmin and wxmax by adding half of the new width to the window’s central X coordinate.

If the world aspect ratio is not greater than the device aspect ratio, then the world window is too tall and thin compared to the device window. In that case, the code calculates the width is needs to give the world window to make it have the same aspect ratio as the device window. It updates wymin and wymax by adding half of the new height to the window’s central Y coordinate.

Now the two windows have the same aspect ratio so the graph is drawn without distortion.

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


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, drawing, graphics, mathematics, reflection, wpf | Tagged , , , , , , , , , , , , , , | 2 Comments