In this chapter, we introduce programming elements for placing and rotating polygons on the screen. The concepts presented are based on the analytical equations derived in Chapter 2.
The functionality of the buttons is as follows: by holding Button3 or Button4 pressed, the polygons keep rotating counterclockwise or clockwise, respectively, until the button is released. For illustrative purposes, we included Button1. This button draws the edges, but it does not fill the polygons. Button2 is used to clear the scene.
Each polygon has a dedicated structure containing data of its properties, such as the coordinates of the vertices, among others, to allow you to keep track of each polygon as the program evolves. This is described in the following sections.
3.1 Polygon Structure
Rectangle Structure
Triangle Structure
TwistedArrow Structure
In Listings 3-1 through 3-3, comments are preceded by the # symbol and last until the end of the line.
As you can observe, the first entry in each structure corresponds to the number of vertices +1. As you will see later, to simplify the code that draws the polygon, it will be helpful to repeat the coordinates of the first vertex at the end of the list.
After the first entry of the structure, the list of the polygon vertices follows. The listing should be written in a clockwise or counterclockwise direction. In this example, we are listing the vertices in a clockwise direction.
The penultimate entry in the list corresponds to the coordinates of the center of rotation of the polygon. These coordinates also correspond to the position of the polygon on the screen and are measured in pixels.
The last entry in the list corresponds to the color of the polygon expressed in the format RGB (red, green, blue). These values must be between zero and one, corresponding to minimum and maximum intensities, respectively.
To exemplify how to access the elements of the structures, we now focus on the rectangle structure.
Rectangle[0] is the first entry of the table. It corresponds to the number of vertices, +1. For this example, the directive Num=Rectangle[0] assigns to the variable Num a value equal to 5.
Rectangle[1] gives the (x,y) coordinates of the first vertex. In this example, the directive x,y =Rectangle[1] will assign to (x,y) the value (1,1). The directive x,y=Rectangle[2] will assign to (x,y) the coordinates of the second vertex, in this case, (1,-1).
It is also possible to individually access the x or y-coordinate. For example, the directive x=Rectangle[2][0] will give the value x=1. The directive y=Rectangle[2][1] gives y=-1. Although accessing individual parameters requires more instructions, the results are the same.
To access the position of the polygon, or equivalently, its center of rotation, you must proceed as follows. First, with the directive Num=Rectangle[0], assign to the variable Num the number of vertices, +1. Now use the directive X0,Y0=Rectangle[Num+1]. This directive will assign to (X0,Y0) the position of the polygon. You can separately obtain the individual values with the X0=Rectangle[Num+1][0] and Y0=Rectangle[Num+1][1] directives.
Finally, the r,g,b=Rectangle[Num+2] directive assigns the RGB color of the polygon stored in the structure, in the variables r,g,b, respectively. You can use the r=Rectangle[Num+2][0], g=Rectangle[Num+2][1], and b=Rectangle[Num+2][2] directives as well. These methods are equivalent and will be used indistinctly in the code.
At this point, we want to mention that semicolons used as ending statements are optional in Python. Furthermore, in Python, semicolons are used to separate directives in a single row. In our code, we end each statement with a semicolon, except for import statements. This usage may be appealing to C programmers.
3.2 Drawing the Edges of the Polygon
Now that we have established the methodology to get access to the elements stored in the polygon structures, as the program evolves, we need to create functions with the capacity of accessing them on demand. To access one of the polygon structures, we use one of the following directives: P=Rectangle, P=Triangle, or P=TwistedArrow. As a result, the variable P will point to a requested polygon, giving us access to the parameters defined in its structure.
- 1.
The value that holds the number of vertices +1 is assigned to the variable Num, by using the directive Num=P[0].
- 2.
The coordinates that correspond to the center of the polygon are assigned to X0 and Y0 using the directive X0,Y0=P[1] or equivalently, by using X0=P[1][0]; Y0=P[1][1].
Pseudocode for Drawing the Edges of a Polygon Pointed by P
The precise code for drawing lines on-screen is described in Section 3.5.
In the algorithm of Listing 3-4, the variables XC and YC correspond to the center of the screen measured in pixels units.
The variable n in the for loop initiates with a value equal to 1 and finishes with the value Num-1, increasing in steps of one unit.
In Python, indentation serves to enclose statements that belong to functions, for loops, conditionals, if, else, and others. You can compare indentation with the pair of braces used in the C language. Therefore, care has to be taken accordingly.
3.3 Filling the Polygon with Lines
Once the edges of the polygon have been drawn, we will fill the polygon with horizontal lines beginning with the polygon minimum y-coordinate up to its maximum. We refer to these values as YMin and YMax, respectively. Each horizontal line is referred to as a raster scan line or simply a scan line.
The filling process consists of drawing lines within the polygon. You have to calculate the x-coordinates that correspond to all the intersections between each scan line and the polygon edges. These intersections are stored in an array and sorted in ascending x-order. In this manner, we proceed to draw the corresponding horizontal segments between pairs of intersecting points.
To determine if a scan line intersects a polygon edge limited between vertices (x1, y1) and (x2, y2), we must first order the vertices such that y1<y2, swapping them if necessary. Then, an intersection will take place if the scanning line lies between y1 and y2. The intersection x-value is calculated as follows.
Equation (3.5) will be used to calculate the intersection of the edges with the scan line.
The function in charge of filling the polygons is FillPolygon(P, B). The parameter P, as described, gives access to a specific polygon. The parameter B is a handle that permits accessing the screen, as described in Section 3.5.
From now on, NumPy is called np and you can access its functions by means of the dot operator. For example, you can use the directive Pi= np.pi. With NumPy, you can use the operations on arrays and mathematical functions.
We will use a parameter named Counter. This parameter will store the count of intersections that may occur between a determined polygon and a scan line. Because the polygon represents a closed figure, these intersections should occur in pairs. Therefore, filling the polygon requires drawing horizontal line segments between pairs of consecutive points.
The filling algorithm, for each scan line, begins by initializing Counter to zero. Each time an intersection occurs, we store the corresponding x-coordinate in Intersect, and we increment Counter in one. At the end, for each scan line, if the condition Counter>1 holds, the intersections have occurred and we proceed to draw the corresponding line segments.
This directive creates the array called Intersect1 . Its size corresponds to the number of intersections between the scan line and the polygon. The array stores the x-coordinates of the intersecting points in ascending order. This way, we can draw the corresponding segments between pairs of intersecting points by utilizing a for loop. It is worth mentioning that Intersect is maintained unaltered.
Function for Filling, Line by Line, the Polygon Pointed by P
The code for drawing the straight segment from (Xintersect1,Y) to (Xintersect2,Y) using colors r,g,b is explained in Section 3.5.
3.4 Rotating the Polygon
The function in charge of rotating the polygons is Rotate(P, Sense). The P parameter receives the structure of the polygon to be rotated. If the Sense parameter is equal to 1, the polygon is rotated clockwise. If it is equal to -1, it is rotated counterclockwise. It is worth mentioning that Rotate(P, Sense) only interacts with the vertices listed in the polygon structure and does not draw on the screen. The vertices of the polygon structure are rotated in steps of five degrees. We will use NumPy to access trigonometric functions.
Algorithm for Rotating a Polygon Pointed by Parameter P
Once a polygon structure has been rotated, to show the polygon in its new state, the old scene is cleared and the new structure is drawn and filled on the screen. Performing this action gives the visual impression of rotating the polygons in real time. The following section describes the basic elements of the Kivy platform needed to draw the polygons on the screen.
3.5 Using the Kivy Platform
Kivy gives you the option of writing your program in two separate files. One of the files, which has a py extension , is where you write most of the program code, whereas in the second file, with a kv extension, is where you declare visual components and controls, also referred to as widgets. As described in the following chapter, we will use Buildozer to construct the Android application, which requires naming the py file main.py. Therefore, we will keep this name from now on.
There is no restriction on the name that the kv file can take; we will name it File.kv. Optionally, it is possible to designate this file equal to the main class in the py file. In this case, the kv file will be loaded automatically.
Our graphics container will be named Form1, and it will correspond to a Kivy FloatLayout type. From a programming point of view, this container is a class in Kivy, as can be seen in the main.py file shown Listing 3-7.
We now analyze the File.kv file, shown in Listing 3-8. Note that Form1 heads the listing and has an ID, assigned by means of the id: Form1 directive.
Our graphics container Form1 is a FloatLayout type and it contains, in turn, a StencilView type region where we will draw the polygons. A StencilView type is convenient here, as nothing will be drawn outside its region. We will name Screen1 our StencilView region by using the directive id: Screen1. Note that in the File.kv listing, similar directives are used for the buttons and the functions that will be in charge of responding to the corresponding events.
In main.py, we need to import two additional libraries, the os and Builder libraries. As we are planning to export this program to Android, we need to provide the absolute location of File.kv. Otherwise, the program will work properly in Ubuntu but it will crash on Android devices.
The first function is executed only once, when the program starts. We take advantage of this characteristic to initialize some variables. The second function executes at every tick of the computer’s clock. This way, at each tick, the program verifies if Button3 or Button4 are pressed by checking the global variable Flag. If this variable is true, the scene is cleared and the polygons are rotated and drawn in their new positions on Screen1. The process is repeated until the button is released.
Now we describe the directives required for drawing a line from (x1, y1) to (x2, y2) on Screen1. First, we have to select a line color, and then we draw the line. The corresponding directives are:
B.ids.Screen1.canvas.add( Color(r,g,b) ).
B.ids.Screen1.canvas.add( Line(points=(int(x1),int(y1),int(x2),int(y2)), width=2) ).
Note that the int(variable) directive takes the integer part of a variable. In the previous code, we chose a line width equal to 2.
main.py Listing
File.kv Listing
3.6 Using Buildozer
Once the program runs properly on Ubuntu, and once you feel satisfied with the results, you can proceed to build your APK, which will be installed and run on an Android device. To accomplish this task, you need Buildozer.
For the conversion process from Ubuntu to Android, you need to create a folder on your computer to be used exclusively for this process. Place the main.py and File.kv files in this folder. Both files must be located in the same folder. Additionally, no other files with py or kv extensions should be in this folder.
Now open a Terminal window by pressing Ctrl+Alt+T and navigate to this folder using the cd command. It is advisable to verify that you are in the correct folder and that your two files are there by using the ls command, which will list the files in the current folder.
can be replaced with a different application name without any problem.
We added numpy and pillow because, in the following chapters, we will need these libraries for image processing.
This will allow you to add text of true-type fonts with extensions .ttf.
We have tested our programs on Android 6, 8, and 10, with no apparent problems.
You should now save the changes made in the buildozer.spec file.
Buildozer will start the conversion process and you will see a lot of reports displayed in the Terminal window. The conversion time depends on the characteristics of your computer. The processing time in a dual-core computer running at 3.2GHz took about 25 minutes. In addition, the first time you run Buildozer, it will download the required platforms as SDK and NDK. You’ll be prompted to accept the corresponding agreements in order for the program to continue.
With this command, Buildozer will install and run the program on the Android device. Detailed documentation about Buildozer is available at https://buildozer.readthedocs.io/en/latest/quickstart.html.
As a final note, it is worth mentioning that once the first APK has been built, you can make changes to the program code. The time required to build the new APK will then decrease drastically, as Buildozer will use all of the resources previously created. The processing time can be as little as one minute, provided that the libraries imported in the program are unaltered. Therefore, you should include all the required libraries the first time you use Buildozer.
3.7 Summary
In this chapter, we presented a screenshot of the program running on an Android cell phone in which we show the placement of the buttons. We described polygon structures containing the necessary data of their properties. We established the methodology to access the data stored in the polygon structures and the methodology to display, fill, and rotate them on the screen. We also described the Kivy platform and the corresponding graphics container in which we constructed the polygons. Finally, we introduced Buildozer and described the conversion process from Ubuntu to Android.