Porting Graphics To BeOS
Volume Number: 14 (1998)
Issue Number: 9
Column Tag: BeTech
Porting Mac Graphics To The BeOS
Written by Dan Parks Sydow
Edited by Bob Boonstra
Translating your Mac application's drawing routines to BeOS code
Overview of Graphics and the BeOS
For the most part, the scope of this article is limited to explaining how to convert your Mac application's graphics-related Toolbox calls to code that integrates into a Be application. To get a more general overview of Mac OS to BeOS porting issues, refer to the MacTech Magazine article "Porting Code to the BeOS" by Michael Rutman, Vol. 13, No. 1 (January 1997). If you don't know the fundamentals of BeOS programming, you'll need to learn how to make use of what Be refers to as the software kits. How to work with the software kits is a topic well beyond the scope of a magazine article - you'll want to refer to one or more of the BeOS programming resources listed at the end of this article. With that said, I still feel compelled to devote just a few paragraphs to summarizing how to use the kits to program the BeOS! After that I move to the real matter at hand - how to go about translating some commonly implemented Mac OS graphics code to BeOS graphics code.
Structure of the BeOS
Be applications run on BeOS-compatible computers, which now include PowerPC-based Macs and most Intel Pentium-based PCs. Between the hardware and a Be application lies the BeOS operating system software that consists of three layers: a microkernel layer that communicates with the computer's hardware, a server layer consisting of a number of servers that each handle the low-level work of common tasks (such as printing), and a software kit layer that holds several software kits - dynamically linked libraries (DLLs) that act as a programmer's interface to the servers and microkernel.
Kits and classes
Collectively, the software kits make up the BeOS application programming interface (API). Each kit consists of a number of object-oriented classes that a programmer makes use of when writing a BeOS program. Together, all the classes in the kits form an application framework. While the code that constitutes your Be application will most likely include objects derived from classes defined in many of the kits, it will always include objects derived from classes in two kits: the Application Kit and the Interface Kit.
A Be application defines an object of a class you derive from the BApplication class. This application object represents the application itself. The BApplication class, obviously enough, is defined in the Application Kit. Other classes in this kit establish a messaging system that makes applications aware of events (such as a click of a mouse button by the user). This kit also give applications the power to communicate with one another.
The Interface Kit is by far the largest of the software kits. The classes of this kit exist to supply applications with a graphical user interface that fully supports user-interaction. The definition of windows and the elements that are contained in windows (such as scroll bars, buttons, lists, and text) are all handled by classes in this kit. Any program that opens a window uses the Interface Kit. Most of the classes mentioned in this article (such as the BRect class for creating rectangles, the BRegion class for creating regions, and the BWindow class for creating windows) are a part of the Interface Kit. In the area of graphics, one of the most important Interface Kit classes is the BView class. Instances of this class - and of the many classes derived from it - are all types of views.
Views
In Macintosh programming, drawing takes place within a port in a window. Somewhat analogous to that on the Be side is the view. In a Be program, drawing doesn't take place directly within a window (a BWindow object). Instead, it takes place within a view (a BView object, or an object derived from the BView class). Unlike a Mac port, however, Be views can be nested. Views aren't readily discernible to the end-user - they exist as a programmer's means of establishing different drawing areas. Typically a window will hold an all-encompassing view, and then one or more views within that main view. Each view is a self-contained drawing environment, so each view can display text and graphics that appear different from that displayed in other views within the same window. That is, each view can display text in its own font, draw lines in a thickness different from lines drawn in other views, and so forth. This technique of allowing each BeOS view to keep track of its own drawing environment deviates from the Mac OS, where drawing parameters are stored in QuickDraw globals.
Because all drawing must take place in a view, everything you see within a window appears in a view. A scroll bar, button, picture, or section of text each lies within its own view. The Be window titled "Hello" in Figure 1, for example, holds several views. Each radio button resides in its own view, as does the one pushbutton. The framed text also exists in its own view. There may also be one large view that holds all these individual views (you're looking at the window as an end-user, so you can't determine if that's true or not!).
Figure 1. A Be window holds a number of views.
A view is capable of responding to a message that's sent from the system to a BWindow object and then on to the view. This messaging system is the principle on which menus and controls work. It is also a topic worthy of much examination, and is beyond the scope of this article. There is one messaging topic worthy of note here, however - window graphics updating, or refreshing.
In the Mac world, a program becomes aware of the need to update a window when the program's event loop call to WaitNextEvent() obtains an event of type updateEvt from the system event queue. On the Be side, when a view needs updating a message is automatically issued by the system. This message is sent to the affected window, and from there it is directed to the proper view. If more than one of a window's views needs updating, more than one message will be sent to the window (one per affected view). The BView class (from which all views are derived) includes a Draw() member function that automatically gets invoked when a view object receives such a message. This Draw() function performs the drawing appropriate for that one view.
Drawing the contents of a BView is often application-specific. In such an instance it's up to your application to override the Draw() function of that view, and to supply the view-drawing code within the new version of the Draw() function. Consider the window shown in Figure 1. If the entire window is obscured, then brought back into view, a number of Draw() functions will be automatically invoked. A radio button is an object of the BRadioButton class, which is derived from the BControl class, which itself is derived from the BView class - so each radio button has a Draw() function. Similarly, the pushbutton is a view - the BButton class is also derived from the BControl class. Each of these three controls have a Draw() function that gets invoked. Fortunately for you, the programmer, these Draw() functions know how to draw the appropriate control without any help from your code. The framed text is of more interest to you. The rectangle and text reside in an object of an application-defined class derived from the generic BView class. Here the BView version of Draw() needs to be overridden and implemented such that it draws the desired contents. Most of the graphics-drawing code covered in this article will become a part of the Draw() function (or of an application-defined function invoked by Draw()) for one of your program's views.
Color
Hopefully your Macintosh application holds the user's attention by making heavy use of color. If it does, and you're interested in porting it to the BeOS, you're in luck - just about any use of color in your Mac program can be easily duplicated in its Be counterpart.
RGB colors
In the Mac OS, QuickDraw allows you to define colors using the RGB color space. In short, a color space is a scheme, or system, for representing colors as numbers. A QuickDraw color is made up of the combination of three color components: red, green, and blue. The Toolbox RGBColor data type consists of three 2-byte unsigned short fields - one for each color component. Each of the three color components is defined by a value with a range of 0 to 65,535. A component value of 0 means none of that component is present in the overall color, while a value of 65,535 means that the component is intensely present in the overall color. The BeOS is capable of defining colors using any of several color spaces, including the default system that defines colors using the RGB system. In Be's version of the RGB system, 32 bits are used to define the color of a single pixel. The Be data type rgb_color is a struct with four fields. Each field (red, green, blue, and alpha) is of type uint8 (an unsigned 8-bit integer), so each component can have a value in the range of 0 (no presence of that component in the color) to 255 (full presence). The remaining eight bits (the alpha field) are reserved for a future implementation of a byte that will be used to specify a transparency level for the overall color. When implemented, an alpha value of 0 will result in a color that is completely transparent, while an alpha value of 255 will result in a color that is completely opaque.
Converting a Mac color to a BeOS color is as easy as redefining your RGBColor variable as an rgb_color variable, scaling the value of each of the three color components accordingly (from a range of 0 to 65,535 on the Mac OS to a range of 0 to 255 on the BeOS). To establish a BeOS color you can assign values to the four fields of an rgb_color variable at the time of declaration. In the following snippet note that the unused alpha field is set to 255 in preparation for the time when Be supports transparency:
rgb_color blueColor = {0, 0, 255, 255}; // {r, g, b, a}
You can also choose to assign each component individually at any time after the variable is declared:
rgb_color redColor;
redColor.red = 255;
redColor.green = 0;
redColor.blue = 0;
redColor.alpha = 255;
Using a color
On the Mac, you typically make use of an RGBColor variable by passing it to the Toolbox function RGBForeColor() to set the foreground color - the color used in all subsequent drawing routines. On the BeOS, an rgb_color variable is most often used in the setting of two colors that a view keeps track of: the view's high color and low color. While these names seem to imply that the two colors must be quite different from one another, that needn't be the case. The high and low colors are simply two colors that can be used individually or mixed together in drawing operations. When drawing takes place in the view, you specify whether the current high color, the current low color, or a mix of the two colors should be used. The BView member functions SetHighColor() and SetLowColor() alter the current high and low colors of a view. Pass SetHighColor() an rgb_color and that color becomes the new high color - and remains as such until the next call to SetHighColor(). The SetLowColor() routine works the same way. This next snippet uses the just-defined redColor and blueColor variables to set a view's high color to red and its low color to blue:
SetHighColor(redColor);
SetLowColor(blueColor);
A call to one of the color-setting functions doesn't actually draw anything - it only sets two color characteristics of a view. Exactly how the high and low colors affect subsequent shape drawing is a topic best covered after the upcoming discussion on patterns.
Allowing the user to choose a color
Assigning numerical values to the red, green, and blue components of a color is something a programmer might not have a problem with - but the end-user shouldn't be forced to select a desired color using this somewhat unfriendly technique. On the Mac, users choose an RGB color by means of the Color Picker. The RGB Picker in the Appearance control panel provides an example of a typical use of the Color Picker. Your own Macintosh program can bring up the Color Picker at any time by calling the Toolbox function GetColor(). Analogous to the Color Picker on the Be is the color control. Your Be program creates an object of the BColorControl class in order to allow a user to select an RGB color - without the user knowing anything about the RGB color system or RGB values. What the BColorControl object displays to the user depends on the number of colors the user's monitor is currently displaying. The user can set that parameter by choosing Screen from the preferences menu in the Tracker. The Screen preferences window itself includes a BColorControl object, so that preferences window is worth examining. Figure 2 shows the Screen window for a monitor set to devote 8 bits to each pixel. Here the color control is the matrix, or block of squares, at the bottom of the Screen window. In Figure 3, with the monitor adjusted to use 32 bits for each pixel, the color control appears as the four horizontal bands at the bottom of the Screen window.
Figure 2. The Be Screen preferences, which includes a color control, at 8 bits per pixel.
Figure 3. The Be Screen preferences, which includes a color control, at 32 bits per pixel.
To include a color control in your own program, declare a BColorControl object and the variables that will be used as arguments to the BColorControl constructor. Then use new to create the new object:
BColorControl *theColorControl;
BPoint leftTop(20.0, 50.0);
color_control_layout matrix = B_CELLS_32x8;
long cellSide = 10;
theColorControl = new BColorControl( leftTop, matrix,
cellSide, "MyColorCntl");
The leftTop argument indicates the pixel coordinate of the left-top corner of the color control. The matrix argument is of the Be-defined data type color_control_layout. When the user has the monitor set to use 8 bits per pixel, the 256 system colors are displayed in a matrix. This parameter specifies how these squares should be arranged. Use one of five Be-defined constants here: B_CELLS_4x64, B_CELLS_8x32, B_CELLS_16x16, B_CELLS_32x8, B_CELLS_64x4. The two numbers in the name of each constant represent the number of columns and rows, respectively, that the colored squares are to be placed in. For instance, the B_CELLS_32x8 constant will display the 256 colors in eight rows, with 32 colored squares in each row. The cellSide argument determines the pixel size of each colored square in the matrix. A value of 10, for instance, results in 256 squares that are each 10-pixels-by-10-pixels in size. The final argument provides a name for the color control object. The BColorControl class is derived from the BControl class, which in turn is derived from the BView class. Every view has a name that can be used elsewhere in your code to access the view. The name can be supplied using a string (as shown in the above snippet) or by passing in a constant variable that was defined as a character constant:
const char *name = "MyColorCntrl";
I've discussed the BColorControl constructor arguments as if they will be used when the user has the monitor set to use 8 bits for the display of color in each pixel. For the argument values used in the previous snippet, the resulting color control looks like the one displayed in the Screen window in Figure 2. If the user instead has the monitor set to 32 bits for each pixel, the same arguments are used in the display of four bands, three of which the user clicks on in order to create a single color (the top, gray band represents the currently unimplemented alpha, or color transparency level component). Here, instead of being used in the determination of the arrangement and size of a matrix of color squares, the arguments are now used to determine the shape and overall size the four bands occupy.
When a window displays a color control, the user selects a color by clicking on its cell (if the user's monitor is set to 8 bits per pixel) or by clicking on a color intensity in each of the three color component bands (if the user's monitor is set to 32 bits per pixel). In either case, the BColorControl object always keeps track of the currently selected color. Your program can obtain this color at any time via a call to the BColorControl member function ValueAsColor(). Here a program obtains the user's current color choice and saves it in the variable userColorChoice:
rgb_color userColorChoice;
userColorChoice = theColorControl->ValueAsColor();
While devoting 32 bits per pixel to a color is a popular technique for defining a program's colors, note that Release 3 of the BeOS includes a 16 bits per pixel option that functions similarly to the 32 bit per pixel option discussed above.
Patterns
Both on the Mac OS and the BeOS, a pattern is an 8-pixel-by-8-pixel area that can be "poured" into any specified area in order to give that entire area a uniform look.
Be-defined patterns
The Mac OS provides five predefined patterns that are a part of the QDGlobals data structure qd: dkGray, ltGray, gray, black, and white. The BeOS has no analogous data structure, but it does supply you with three patterns that are always available for your program's use. Each of these patterns is represented by a Be-defined constant: B_SOLID_HIGH is a solid fill of the current high color, B_SOLID_LOW is a solid fill of the current low color, and B_MIXED_COLORS is a checkerboard pattern of alternating current high color and current low color pixels. Earlier you saw how to define a color and then use it to set the high or low color of a view. Now that you know about patterns, you're ready for an example of using an RGB color. This next bit of code draws a solid red rectangle:
BRect theRect(20.0, 10.0, 220.0, 110.0); // l, t, r, b
rgb_color redColor = {255, 0, 0, 255};
SetHighColor(redColor);
FillRect(theRect);
The above snippet introduces the BRect class. I cover this class in more depth ahead, but I've brought it into play now because I need to draw something here! The BRect class is used to create a BRect object, which serves to define a rectangle. Unlike most classes in the BeOS API, the BRect is thought of more as a primitive data type than as a class: it's not derived from any other class, all its data members are public, and it includes no virtual member functions. Typically, you create a rectangle object by simply declaring a BRect variable - you don't use new to allocate memory for the object. As shown above, assigning pixel coordinates to the rectangle can be done by listing them in the order left, top, right, bottom at the rectangle object's declaration. Like the Mac OS, the BeOS uses a coordinate system based on coordinate pairs: the top-left pixel is considered pixel (0.0, 0.0). Pixel numbering increases as you move to the right, and as you move down. Unlike in the Mac OS, pixels are floating point values in the BeOS. Integral BeOS coordinate values fall in the center of a screen pixel, unlike the Mac OS, where the coordinate value represents the upper left corner of a screen pixel. Again like the Mac OS, on the BeOS the screen itself is a global coordinate system, and each window has its own local coordinate system. Additionally, every view in a BeOS window has its own coordinate system.
In general, to draw in a view you call a BView drawing function. In particular, to draw a rectangle in a view you call the BView function FillRect(). Consider yourself forgiven if your first thought was that FillRect() must be a member function of the BRect class rather than the BView class! By default, FillRect() fills the specified rectangle with the high color. The above snippet sets the high color to red - so that's the color the rectangle theRect will be. You can also tell FillRect() to use a particular pattern (whether one of the three Be-defined patterns or an application-defined pattern) by including an optional second argument. This next snippet defines a blue color and uses it in the setting of the low color. The line that follows draws the rectangle theRect in blue:
rgb_color blueColor = {0, 0, 255, 255};
SetLowColor(blueColor);
FillRect(theRect, B_SOLID_LOW);
Creating a pattern
For monochrome patterns on the Mac, you can use the above-mentioned qd patterns, the 38 patterns that make up the System pattern list, or define your own 'PAT ' pattern resources in a resource editor. For color patterns you can define your own 'ppat' pixel pattern resources. On the BeOS you define patterns within your source code. Using a variable of the data type pattern you specify whether each of the 64 pixels of a pattern should be filled with the current high color or current low color. Here's the pattern data type:
typedef struct {
uchar data[8];
} pattern;
To create a pattern you declare a pattern variable and initialize each of its eight fields with a hexadecimal pair. Each of the eight elements in the pattern array is one byte in size, so each can hold a single unsigned value in the range of 0 to 255. Each of the hexadecimal pairs in each of the eight rows in a pattern falls into this range (0x00 = 0, 0xff = 255). To create a pattern variable, determine the hexadecimal pairs for the pattern and assign the variable those values. In Figure 4 you see that I drew an 8-by-8 grid and filled in the squares that I want to be represented by the high color, and left blank the squares I want to be represented by the low color. The left side of the figure shows the binary representation of each row in the pattern, with a row looked at four bits at a time. The right side of the figure shows the corresponding hexadecimal value for each row.
Figure 4. The binary and hexadecimal representations of a pattern.
To convert a Macintosh pattern, view it in your Mac resource editor and determine which pixels you want "on" and which you want "off." Then write down the numerical representation of the pattern, as I did in Figure 4. At this time you won't be concerned with pattern color - just consider that an "on" pixel will be one color, and an "off" pixel will be a different color.
In Figure 4, the binary representation of each pixel row is for the benefit of showing how I came up with the hexadecimal row value. When initializing the pattern variable, use the hexadecimal values - like this:
pattern stripePattern = { 0xcc, 0x66, 0x33, 0x99,
0xcc, 0x66, 0x33, 0x99};
Using a pattern
On the Mac, you make use of a pattern in a variety of ways. If the pattern is one of the five qd-defined patterns, you simply use a pointer to it in a drawing routine - as in passing &qd.black to FillRect(). If the pattern is included in a pattern list (a resource of type 'PAT#'), then you use a call to the Toolbox function GetIndPattern() to load the desired pattern to use. If the pattern is an individual resource of type 'PAT ', then you load it by calling the Toolbox function GetPattern(). You've seen that on the BeOS there is no qd global data structure, and that patterns don't exist as resources. So in all cases you should draw out the Mac pattern and define it as a BeOS pattern variable, as shown above.
On its own, a Be pattern variable holds no color information. When it comes time to use a pattern variable, the colors used in the pattern are dependent on the current high and low colors. In this next snippet a striped pattern is defined, and the high color is set to red and the low color to blue. When the rectangle theRect is filled with the striped pattern, it will end up with red and blue diagonal stripes:
BRect theRect(20.0, 10.0, 220.0, 110.0);
rgb_color redColor = {255, 0, 0, 255};
rgb_color blueColor = {0, 0, 255, 255};
pattern stripePattern = { 0xcc, 0x66, 0x33, 0x99,
0xcc, 0x66, 0x33, 0x99};
SetHighColor(redColor);
SetLowColor(blueColor);
FillRect(theRect, stripePattern);
Drawing Pen
Like the Mac OS, the BeOS uses the concept of an imaginary graphics pen when carrying out drawing operations. On both operating systems, the pen exists as a way to summarize and express properties of a drawing environment.
Pen Location
Mac programmers call the Toolbox functions Move() and MoveTo() to establish the starting location for the next drawing operation. On the BeOS convert calls to these routines to calls to the BView functions MovePenBy() and MovePenTo(). Pass the Be function MovePenTo() a pair of float values representing the coordinates of the pixel to move to:
MovePenTo(20.0, 70.0);
Like MovePenTo(), the MovePenBy() function moves the starting location for drawing. MovePenTo() moves the pen relative to the view's origin. MovePenBy() moves the pen relative to its current location in the view. Consider this snippet:
MovePenTo(20.0, 70.0);
MovePenBy(40.0, 10.0);
The call to MovePenTo() moves the pen to the location 20 pixels right of the view origin and 70 pixels down from the top of the view. That places the pen at the point (20.0, 70.0). The call to MovePenBy() uses this current location as the reference and moves the pen 40 pixels to the left and 10 pixels down. The result is that, relative to the view's origin, the pen is at the point (60.0, 80.0).
As with the Mac Toolbox function Move(), negative values for the BeOS function MovePenBy() move the pen "backwards" in the view. A negative horizontal argument moves the pen to the left, while a negative vertical argument moves the pen up.
Pen Size
On the Mac OS, the pen's size - the thickness at which lines are drawn - is by default one pixel in width and one pixel in height. This pen size can be altered with a call to the Toolbox function PenSize(). On the BeOS, the pen has a single thickness which results in lines having the same width and height. You call the BView function SetPenSize() to alter this pen diameter:
SetPenSize(3.0);
When altering the pen size, the best technique is to obtain and save the current pen size, change it, perform the desired drawing using the new pen size, then restore the pen to the saved size. The BView member function PenSize() allows you to do that. When invoked, PenSize() returns a float that holds the current thickness of the pen:
float savedPenSize;
savedPenSize = PenSize();
Shapes
I've already demonstrated how to use the graphics pen, what the coordinate system is, and how to set up and draw a rectangle. As you may have guessed, you're well on your way to drawing just about any shape in your Be program.
Points
On the Mac, a point is represented by a variable of the type Point. The Point is a data structure consisting of two short values: h is the horizontal coordinate of the point, while v is the vertical coordinate. On the BeOS, a point is represented by a BPoint object. The BPoint object consists of two floating point values: x denotes the horizontal coordinate, while y denotes the vertical coordinate.
A BPoint object can have values assigned to its coordinate pair members either at the time of declaration or anytime thereafter. At declaration, one of the BPoint constructors can be used to take care of the task of assigning values to the BPoint members:
BPoint thePoint(40.0, 70.0);
If the assignments are to be made after the object's declaration, then this can be done either by using the BPoint member function Set() or by assigning values directly to the x and y data members:
BPoint thePoint;
BPoint anotherPoint;
thePoint.Set(100.0, 200.0);
anotherPoint.x = 100.0;
anotherPoint.y = 200.0;
Like the BRect class, the BPoint class acts much like a data type. You create a point object by simply declaring a BPoint variable (as opposed to allocating memory using new), and you can alter its data members either by direct assignment or by invoking the Set() member function.
Lines
To draw a line on the Mac you position the graphics pen using calls to Move() or MoveTo(), then invoke the Toolbox function Line() or LineTo(). To draw the same line under the BeOS, call MovePenBy() or MovePenTo(), then invoke the BView function StrokeLine().
BPoint start(50.0, 50.0);
BPoint end(150.0, 50.0);
MovePenTo(start);
StrokeLine(end);
Optionally, you can also omit the pen-placement and simply pass StrokeLine() two parameters: each a BPoint object that represents the starting and ending point of the line, respectively:
StrokeLine(start, end);
Both versions of StrokeLine() offer a final optional parameter that can be used to specify a pattern in which the line is drawn. Here the diagonal stripe pattern discussed earlier in this article is used. The high color is set to red, and the low color is left in its default state of white. Additionally, the pen size is set to 10.0. The result is a line 10 pixels in thickness with diagonal red stripes running through it:
rgb_color redColor = {255, 0, 0, 255};
BPoint start(50.0, 50.0);
BPoint end(150.0, 50.0);
SetHighColor(redColor);
SetPenSize(10.0);
StrokeLine(start, end, stripePattern);
Rectangles
I've already described how to set up and fill a rectangle - so I can be brief here as I formalize the explanations. The BRect class has four floating-point data members (left, top, right, and bottom) that are used to specify the coordinates of a rectangle. You can assign these data members values in a variety of ways. At the rectangle variable's declaration you can assign the members values individually:
BRect theRect(10.0, 30.0, 110.0, 130.0);
Alternatively, you can set up the rectangle by specifying the left-top coordinate and right-bottom coordinate:
BPoint leftTopPt(10.0, 30.0);
BPoint rightBottomPt(110.0, 130.0);
BRect anotherRect(leftTopPt, rightBottomPt);
On a Mac you set up a rectangle either by making direct assignments to a Rect variable's left, top, right, and bottom fields, or by calling the Toolbox function SetRect(). On the BeOS a BRect object's data members are similarly given values: use direct assignments or call the BRect function Set().
BRect theRect;
BRect anotherRect;
theRect.left = 10.0;
theRect.top = 30.0;
theRect.right = 110.0;
theRect.bottom = 130.0;
anotherRect.Set(10.0, 30.0, 110.0, 130.0);
The BRect class also contains member functions LeftTop(), LeftBottom(), RightTop(), and RightBottom() that provide access to any of the points in the rectangle. Similarly, there are functions like SetLeftTop() to modify those point values.
After setting up a rectangle in your Macintosh program, you frame it by calling FrameRect(), fill it with the current pattern (as established by a call to the Toolbox function PenPat() or PenPixPat()) by calling PaintRect(), or fill it with a specified pattern by calling FillRect(). In a Be program, frame the rectangle by calling StrokeRect(), and fill it by calling FillRect(). Both StrokeRect() and FillRect() allow for an optional second argument that specifies the pattern to use in drawing the rectangle. If omitted, a solid pattern using the current high color (B_SOLID_HIGH) is used by default.
BRect theRect(10.0, 10.0, 160.0, 160.0);
BRect anotherRect(30.0, 30.0, 140.0, 140.0);
StrokeRect(theRect, B_MIXED_COLORS);
FillRect(anotherRect, stripePattern);
Round rectangles
Both the Mac OS and the BeOS rely on the rectangle (rather than a unique round rectangle data type or class) as the basis for a rectangle with rounded corners. On the Mac you've used the Macintosh Toolbox functions FrameRoundRect(), PaintRoundRect() and FillRoundRect(). On the BeOS you use the BView functions StrokeRoundRect() and FillRoundRect(). For your Be round rectangle, first declare a BRect variable and set up the rectangle. Then pass that rectangle and the amount of rounding that is to be applied to each corner as arguments to StrokeRoundRect() or FillRoundRect(). The degree of rounding is determined just as it is for a Mac round rectangle: the second and third arguments together specify the shape of an ellipse (the second parameter establishes the ellipse radius along the x-axis, while the third parameter establishes the ellipse radius along the y-axis). Such an ellipse can be thought of as being set in each corner of the rectangle. Then, only one quarter of the ellipse is actually drawn in order to form a rounded corner. The following snippet demonstrates how to create a round rectangle that's filled with the current low color:
BRect theRect(30.0, 30.0, 130.0, 130.0);
FillRoundRect(theRect, 20.0, 20.0);
Ellipses
After defining a rectangle, an ellipse is drawn by inscribing an oval within the boundaries of the rectangle. The rectangle itself isn't drawn - it simply serves to specify the size of the ellipse. This is similar to how an ellipse is drawn in a Mac program. In the Mac OS you pass a pointer to a rectangle to FrameOval(), PaintOval(), or FillOval(). In the BeOS, you use the BView member functions StrokeEllipse() and FillEllipse():
BRect theRect(30.0, 30.0, 130.0, 130.0);
FillEllipse(theRect, B_SOLID_LOW);
StrokeEllipse(theRect, B_SOLID_HIGH);
Polygons
A polygon is a closed shape that has three or more sides. Your Macintosh program creates a polygon by declaring a PolyHandle variable and then calling OpenPoly() and ClosePoly(). Between the open and close calls should be a MoveTo() call that positions the graphics pen, and a series of calls to LineTo() that then draw the lines that make up the edges of the polygon. Rather than a series of edge-defining lines, the BeOS treats a polygon as a series of vertex-defining points. To create a Be polygon, first declare an array of BPoints. The number of points should be the number of polygon vertices. Use the BPoint function Set() to define each vertex. Then declare and create a new BPolygon object. The BPolygon constructor accepts as its arguments the array of BPoints along with an int32 (32-bit integer) value that specifies how many points are in the array. Here's how a BPolygon object representing a three sided polygon might be defined:
BPoint pointArray[3];
int32 numPoints = 3;
BPolygon *thePolygon;
pointArray[0].Set(50.0, 100.0);
pointArray[1].Set(150.0, 20.0);
pointArray[2].Set(250.0, 100.0);
thePolygon = new BPolygon(pointArray, numPoints);
On the Mac, after a call to ClosePoly() is made, the lines that make up the polygon are treated as a single entity. The polygon can be drawn using the Toolbox functions FramePoly(), PaintPoly(), and FillPoly(). In your Be program, once a BPolygon object is defined, its outline can be drawn by calling the BView member function StrokePolygon(), or it can be filled in by calling FillPolygon(). As with other drawing routines, omitting the pattern argument tells the function to use B_SOLID_HIGH as the fill pattern. If you want to use a different pattern, specify it as a second argument. Here I'm filling the just-defined polygon with my earlier-created striped pattern:
FillPolygon(thePolygon, stripePattern);
Regions
A Macintosh region is formed much as a Macintosh polygon is: a series of calls to Toolbox drawing functions define the area that's to be considered one region. A new RgnHandle is created using a call to NewRgn(), and OpenRgn() and CloseRgn() calls frame the region-defining Toolbox calls. A BeOS region is instead created by defining any number of rectangles that are then grouped together into a single BRegion object. The rectangles that make up a region can vary in size, and can be defined such that they form one continuous shape or any number of seemingly unrelated shapes (that is, the one region can be composed of non-touching rectangles). To set up a region, first create a new BRegion object. Then define a rectangle and add that rectangle to the BRegion object. The steps of defining and adding rectangles to the BRegion object can be repeated until the area that is to be considered a single region is complete. In the following snippet two rectangles are defined and added to a BRegion object:
BRect theRect;
BRegion *theRegion;
theRegion = new BRegion();
theRect.Set(20.0, 20.0, 70.0, 70.0);
theRegion->Include(theRect);
theRect.Set(50.0, 50.0, 150.0, 100.0);
theRegion->Include(theRect);
On a Mac you frame and fill a region using calls to FrameRgn() and FillRgn(). Because a Be region can consist of any number of overlapping rectangles, outlining each individual rectangle would result in lines running through the content area of the region - so no StrokeRegion() function exists. Instead, display the region by invoking the BView member function FillRegion(). Here the region that was created in the previous snippet is filled with the default B_SOLID_HIGH pattern:
FillRegion(theRegion);
One important use of a region is in testing for the inclusion of a point within an area. On the Mac you use the Toolbox function PtInRgn() to see if the cursor was over the area of a region when the mouse button was clicked. On the BeOS, test a point for inclusion in a region by calling the BRegion member function Contains(). Pass this routine the BPoint object to test, and Contains() returns a bool (Boolean) value that indicates whether or not the tested point lies within the region. In this next snippet a region consisting of just a single rectangle is defined. A point is then defined and tested for inclusion in this region:
BRect theRect(20.0, 20.0, 70.0, 70.0);
BRegion *theRegion;
BPoint thePoint(60.0, 90.0);
theRegion = new BRegion();
theRegion->Include(theRect);
FillRegion(theRegion);
if (theRegion->Contains(thePoint)) // point in region
// do something
else // point not in region
// do something else
Last Word
For learning new programming techniques, there's no better way to get the complete picture than to view a well-commented source code listing for a complete program. Rather than use up valuable magazine pages, I've opted to make such a listing available at MacTech's web site. After downloading, view the code by opening the GraphicsEx.cp file from within the BeIDE (if you have the BeOS, you have a version of Metrowerks' CodeWarrior for BeOS). To make use of the code, create a new BeIDE project, add GraphicsEx.cp to it, and then choose Run from the Project menu.
I've included a number of graphics-porting considerations in this article, but of course there are many more. How to move your Mac application's picture-drawing and bitmap-handling code to the BeOS are a couple of topics that come to mind. For information on these subjects, as well as more general BeOS programming information, refer to the BeOS API reference book "Be Developer's Guide" by O'Reilly & Associates, or to my own BeOS tutorial "Programming the Be Operating System," also by O'Reilly & Associates.
Dan Parks Sydow is the author of over a dozen programming books, "Programming the Be Operating System" by O'Reilly & Associates. Dan recently had the pleasure of visiting the Menlo Park, CA headquarters of Be, Inc. to talk with Be Chairman and CEO Jean-Louis Gassee and a number of Be engineers. He left thoroughly convinced that the BeOS is destined to be an important digital media tool for both programmers and end-users.