© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2021
M. Cywiak, D. CywiakMulti-Platform Graphics Programming with Kivyhttps://doi.org/10.1007/978-1-4842-7113-1_3

3. Two-Dimensional Polygon Programming

Moisés Cywiak1   and David Cywiak2
(1)
Leon, Guanajuato, Mexico
(2)
Queretaro, Mexico
 

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.

Although there is no limit to the number of vertices and edges that the polygons can take, for simplicity for this working example, we selected three simple shapes—a square, a triangle, and a skewed-arrow—placed at different positions on the screen. Figure 3-1 shows a screenshot of the program running on an Android cell phone with a screen resolution of 480x680. The polygons are closed figures.
../images/510726_1_En_3_Chapter/510726_1_En_3_Fig1_HTML.jpg
Figure 3-1

Screenshot of the program running on an Android cell phone

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

The structures for the three polygons used in the example are shown in Listings 3-1 through 3-3.
Rectangle=[
    5,         #Number of vertices +1
    [1, 1],    #Vertex 1
    [1, -1],   #Vertex 2
    [-1,-1],   #Vertex 3
    [-1, 1],   #Vertex 4
    [1,1],     #Vertex 1 repeated to allow closing #the polygon
    [60,120],  # Position of the polygon and  center #of rotation measured in screen units
    [1,0,0]    #Red, green, blue color. Entries must #be between 0 and 1
      ]
Listing 3-1

Rectangle Structure

Triangle=[
    4,         ##Number of vertices +1
    [1, -1],   #Vertex 1
    [-1, -1],  #Vertex 2
    [0, 1],    #Vertex 3
    [1, -1],   #Vertex 1 repeated
    [-80,-90], #Position and center of rotation
    [1,0,1]    #Color (r,g,b)
      ]
Listing 3-2

Triangle Structure

TwistedArrow=[
    8,               #Number of vertices +1
    [0.6, 0.7],  #Vertex 1
    [1.0, -0.7], #Vertex 2
    [-0.7,-1.0], #Vertex 3
    [-0.2,-0.2], #Vertex 4
    [-1.0, 0.5], #Vertex 5
    [0.0, 1.0],  #Vertex 6
    [0.3, 0.3],  #Vertex 7
    [0.6, 0.7],  #Vertex 1 repeated
    [0,0],       #Position and center of rotation
    [0,1,0.6]    #Color (r,g,b)
     ]
Listing 3-3

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.

To draw the polygon’s sides, we need to note the number of vertices. We also need the coordinates of its center. We read these values in the next two steps, as follows:
  1. 1.

    The value that holds the number of vertices +1 is assigned to the variable Num, by using the directive Num=P[0].

     
  2. 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].

     
Finally, to draw the edges, we use a for loop using a variable n ranging from the first vertex to the last vertex. See the pseudocode in Listing 3-4.
for n in range(1,Num):
      x1=XC+ScaleX*P[n][0]+X0;
y1=YC+ScaleY*P[n][1]+Y0;
      x2=XC+ScaleX*P[n+1][0]+X0;
y2=YC+ScaleY*P[n+1][1]+Y0;
      r=P[Num+2][0]; g=P[Num+2][1];
b=P[Num+2][2];
      Draw a line from (x1,y1) to (x2,y2) using color(r,g,b).
Listing 3-4

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.

First, we write the equation of the straight line that passes through the vertices as:
$$ y= mx+b $$
(3.1)
In Equation (3.1), m and b represent the slope and the y-intercept of the straight line, respectively. In Equation (3.1), using vertices (x1, y1) and (x2, y2) gives:
$$ m=frac{left({y}_2-{y}_1
ight)}{left({x}_2-{x}_1
ight)} $$
(3.2)
and
$$ b={y}_1-{mx}_1 $$
(3.3)
Using Equations (3.1) and (3.3) gives:
$$ x=frac{y-{y}_1}{m}+{x}_1 $$
(3.4)
In Equation (3.4), the y value corresponds to the scan line, then the x-value will correspond to the intersection of the edge with the scan line, provided that y1 < y < y2. By defining, $$ M=frac{1}{m} $$, we can rewrite Equation (3.4) as:
$$ x=Mleft(y-{y}_1
ight)+{x}_1 $$
(3.5)

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.

At this point, we need NumPy. To get access to this library, use the following directive:
import numpy as np

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.

In this program, we create an array named Intersect to store up to 30 intersection points. For the working example, we do not expect to store more than 30 intersection points. For more elaborate polygons, this number can be increased. The allocated memory region is obtained by means of this directive:
Intersect=np.zeros(30).

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.

Finally, to simplify filling the polygon, a new array called Intersect1 receives and sorts all the intersection values collected in Intersect in ascending order. It does so by utilizing this directive:
Intersect1=np.sort(Intersect[0:Counter])

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.

The pseudocode for filling the polygon pointed by the parameter P is shown in Listing 3-5.
Intrsect=np.zeros(30); #Array to store up to 30
     #intersection points
def FillPolygon(P, B):
    Num=P[0]; X0=P[Num+1][0]; Y0=P[Num+1][1]; #Reading number of vertices and polygon center
    r,g,b=P[Num+2]; #Reading filling color
    #Calculating polygon YMin, Ymax
    YMin=P[1][1]; YMax=P[1][1]
    for n in range(1,Num):
        if YMin>P[n][1]:
            YMin=P[n][1];
        if YMax<P[n][1]:
            YMax=P[n][1];
    #We have now (YMin, YMax)
    #We now proceed filling lines between Ymin and #Ymax
    for y in np.arange (YMin, YMax, 0.03): # numpy #allows to increment the for loop in decimal steps
        Counter=0;
        for n in range(1, Num): #We will search #intersections edge by edge
            #We first order the two vertices of each #edge such that Y1<Y2
            if ( P[n][1] <P[n+1][1] ):
                Y1=P[n][1];   X1=P[n][0];
                Y2=P[n+1][1]; X2=P[n+1][0];
            else:
                Y1=P[n+1][1]; X1=P[n+1][0];
                Y2=P[n][1];   X2=P[n][0];
            if (Y1<y and y<Y2):
                if (Y2-Y1)!=0:
                    M=(X2-X1)/(Y2-Y1);
                else:
                    M=1.0e8;  # if Y1=Y2, the edge #slope is infinite. We assign to it a large value
                Intersect[Counter]=(y-Y1)*M+X1; #We #store the x value
                Counter=Counter+1; # And we #increment Counter as a new value has being stored
        if(Counter>1):
            Intersect1=np.sort(Intersect[0:Counter]);#Intersect1 #contains ordered pair of x values
            for n in range(0,Counter,2): # We now #trace lines between pairs of intersections
                XIntersect1=XC+ScaleX*Intersect1[n] +  X0
                XIntersect2=XC+ScaleX*Intersect1[n+1] + X0
                Y=YC+ScaleY*y+Y0;
                Draw_Line(XIntersect1,Y, XIntersect2,Y) using r,g,b colors.
Listing 3-5

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.

The algorithm is shown in Listing 3-6.
def Rotate(P, Sense):
    if Sense==-1:
        Teta=np.pi/180*(-5);
    else:
        Teta=np.pi/180*(5);
    Cos_Teta=np.cos(Teta)
    Sin_Teta=np.sin(Teta);
    Num=P[0];
    for n in range(1,Num+1):
        XP=P[n][0]*Cos_Teta  +  P[n][1]*Sin_Teta;
        YP=-P[n][0]*Sin_Teta +  P[n][1]*Cos_Teta;
        P[n][0]=XP
        P[n][1]=YP
Listing 3-6

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.

In the main.py file, we declare two functions that respond to the computer clock ticks. One of them is as follows:
Clock.schedule_once(Handle,Initialize)
The second one is as follows:
Clock.schedule_interval(Handle.Temporal,0.03)

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.

Finally, each event button provides us with a handle for accessing the Kivy components (widgets). The handles correspond to the B variable in the functions. To access Screen1, we use the following:
B.ids.Screen1

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.

The complete code for main.py and File.kv is shown in Listings 3-7 and 3-8, respectively.
#Import required libraries
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics import Line, Color
from kivy.clock import Clock
import os
import numpy as np
from kivy.lang import Builder #Android requires the absolute path
Builder.load_file(
    os.path.join(os.path.dirname(os.path.abspath(__file__)), 'File.kv')
    )
from kivy.config import Config
Config.set("graphics","resizable", False)  # Avoid #Form1 of being re-sizable
#Our cell-#phone is 480 by 680 pixels
Config.set('graphics', 'width',  '480');
Config.set('graphics', 'height', '680');
#XC,YC,W and H will be set by the function #Initialize()
#after Clock.schedule_once has been executed.
XC=0; YC=0; W=0; H=0;
Flag=False; #If False not drawing allow
#=======Polygon structures   =============
#The first entry corresponds to the number of #vertexes+1.
#Following pair of entries correspond to the list of #vertices ordered in clockwise direction.
#The first vertex is repeated at end of the list of #vertices.
#Following pair of entries correspond to the center #of rotation in pixels or screen coordinates.
#The triplet at the end of the structure corresponds #to the polygon color: red, green, blue.
Rectangle=[
    5,         #Four vertices +1
    [1, 1],    #Vertex 1
    [1, -1],   #Vertex 2
    [-1,-1],   #Vertex 3
    [-1, 1],   #Vertex 4
    [1,1],     #Vertex 1 repeated to allow drawing #the last edge of the polygon
    [60,120],  #Center of rotation
    [1,0,0]    #Red, green, blue color. Entries must #be between 0 and 1
      ]
Triangle=[
    4,         # Three vertices+1
    [1, -1],   #Vertex 1
    [-1, -1],  #Vertex 2
    [0, 1],    #Vertex 3
    [1, -1],   #Vertex 1 repeated
    [-80,-90], #Center of rotation
    [1,0,1]    #Color (r,g,b)
      ]
TwistedArrow=[
    8,           #Seven vertices +1
    [0.6, 0.7],  #Vertex 1
    [1.0, -0.7], #Vertex 2
    [-0.7,-1.0], #Vertex 3
    [-0.2,-0.2], #Vertex 4
    [-1.0, 0.5], #Vertex 5
    [0.0, 1.0],  #Vertex 6
    [0.3, 0.3],  #Vertex 7
    [0.6, 0.7],  #Vertex 1 repeated
    [0,0],       #Center of rotation
    [0,1,0.6]    #Color (r,g,b)
     ]
#Here, we choose some scaling terms
ScaleX=110; ScaleY=110;
def DrawEdges(P, B):
    Num=P[0]; X0=P[Num+1][0]; Y0=P[Num+1][1];
    for n in range(1,Num):
        x1=XC+ScaleX*P[n][0]+X0;    y1=YC+ScaleY*P[n][1]+Y0;
        x2=XC+ScaleX*P[n+1][0]+X0;  y2=YC+ScaleY*P[n+1][1]+Y0;
        r=P[Num+2][0]; g=P[Num+2][1]; b=P[Num+2][2];
        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) );
def Rotate(P, Sense):
    if Sense==-1:
        Teta=np.pi/180*(-5);
    else:
        Teta=np.pi/180*(5);
    Cos_Teta=np.cos(Teta)
    Sin_Teta=np.sin(Teta);
    Num=P[0];
    for n in range(1,Num+1):
        XP=P[n][0]*Cos_Teta  +  P[n][1]*Sin_Teta;
        YP=-P[n][0]*Sin_Teta +  P[n][1]*Cos_Teta;
        P[n][0]=XP
        P[n][1]=YP
#Function for filling the polygon line by line
Intersect=np.zeros(30); #Array to store up to 30 #intersection points
def FillPolygon(P, B):
    Num=P[0]; X0=P[Num+1][0]; Y0=P[Num+1][1]; #Reading number of vertices and polygon center
    r,g,b=P[Num+2]; # reading polygon color
    #Calculating polygon YMin, Ymax for limiting #number of line scans
    YMin=P[1][1]; YMax=P[1][1]
    for n in range(1,Num):
        if YMin>P[n][1]:
            YMin=P[n][1];
        if YMax<P[n][1]:
            YMax=P[n][1];
    #We have now (YMin, YMax)
    #We now proceed filling lines between Ymin and #Ymax
    for y in np.arange (YMin, YMax, 0.03): # numpy #allows increasing y in steps of 0.03 in the for loop
        Counter=0;
        for n in range(1, Num): #We will search line #cuts segment by segment
            #We first order the two vertices of each #segment such that Y1<Y2
            if ( P[n][1] <P[n+1][1] ):
                Y1=P[n][1];   X1=P[n][0];
                Y2=P[n+1][1]; X2=P[n+1][0];
            else:
                Y1=P[n+1][1]; X1=P[n+1][0];
                Y2=P[n][1];   X2=P[n][0];
            if (Y1<y and y<Y2):
                if (Y2-Y1)!=0:
                    M=(X2-X1)/(Y2-Y1);
                else:
                   # if Y1=Y2, the slope is infinite. We #assign to it a large value. This avoids an #additional if
                    M=1.0e8; #
                Intersect[Counter]=(y-Y1)*M+X1; #We #store the x value
                Counter=Counter+1; # And we #increment Counter as a new value has being stored
        if(Counter>1 and Counter <30): # Geometrical #closed figure.
            Intersect1=np.sort(Intersect[0:Counter]);#Intersect1 #holds ordered pair of x values
            for n in range(0,Counter,2): # We now #trace lines between pairs of intersections
               XIntersect1=XC+ScaleX*Intersect1[n] + X0
               XIntersect2=XC+ScaleX*Intersect1[n+1] + X0
                Y=YC+ScaleY*y+Y0;
                #Picking the color and drawing the #horizontal line between x pairs
                B.ids.Screen1.canvas.add(  Color(r,g,b) );
                B.ids.Screen1.canvas.add( Line(points=(int(XIntersect1),int(Y), int(XIntersect2),int(Y)),
                width=2) ) ;
class Form1(FloatLayout):
    def __init__(Handle, **kwargs):
        super(Form1, Handle).__init__(**kwargs);
        Event1=Clock.schedule_once(Handle.Initialize);
        Event2=Clock.schedule_interval(Handle.Temporal,0.03);
    def Initialize(B, *args):
        global W,H, XC,YC;
        W,H=B.ids.Screen1.size;
        XI,YI=B.ids.Screen1.pos
        XC=XI+int (W/2);
        YC=YI+int(H/2);
    def Temporal(B, *args):
        global Flag;
        if (Flag==True):
            if (B.ids.Button3.state=="down"):
                Sense=-1;
            if (B.ids.Button4.state=="down"):
                Sense=1;
            B.ids.Screen1.canvas.clear(); # Clear the #scene
            Rotate(Rectangle, Sense);
            DrawEdges(Rectangle,B);
            FillPolygon(Rectangle,B);
            Rotate(Triangle,Sense);
            DrawEdges(Triangle,B);
            FillPolygon(Triangle,B);
            Rotate(TwistedArrow,Sense);
            DrawEdges(TwistedArrow,B);
            FillPolygon(TwistedArrow,B);
    def Button1_Click(B):
        DrawEdges(Rectangle, B);
        DrawEdges(Triangle, B);
        DrawEdges(TwistedArrow,B);
    def Button2_Click(B):
        B.ids.Screen1.canvas.clear();
    def Button3_Click(B):
        global Flag;
        Flag=True;
    def Button3_Release(B):
        global Flag;
        Flag=False;
    def Button4_Click(B):
        global Flag;
        Flag=True;
    def Button4_Release(B):
        global Flag;
        Flag=False;
# This is the Start Up code.
class StartUp (App):
    def build (BU):
        BU.title="Form1"
        return Form1();
if __name__ =="__main__":
    StartUp().run();
Listing 3-7

main.py Listing

#:set W 440
#:set H 440
<Form1>:
    id : Form1
    StencilView:
        id: Screen1
        size_hint: None,None
        pos_hint: {"x":0.04, "y":0.25}
        size: W,H
        canvas.before:
            Color:
                rgba: 0.9, 0.9, 0, 1
            RoundedRectangle:
                pos:  self.pos
                size: self.size
    Button:
        id: Button1
        on_press: Form1.Button1_Click()
        text: "Button1"
        size_hint: None,None
        pos_hint: {"x": 0.2, "y":0.05}
        size: 100,30
    Button:
        id: Button2
        on_press: Form1.Button2_Click()
        text: "Button2"
        size_hint: None,None
        pos_hint: {"x": 0.63, "y":0.05}
        size: 100,30
    Button:
        id: Button3
        on_press: Form1.Button3_Click()
        on_release: Form1.Button3_Release()
        text: "Button3"
        size_hint: None,None
        pos_hint: {"x": 0.05, "y":0.16}
        size: 100,30
    Button:
        id: Button4
        on_press: Form1.Button4_Click()
        on_release:  Form1.Button4_Release()
        text: "Button4"
        size_hint: None,None
        pos_hint: {"x": 0.73, "y":0.16}
        size: 100,30
Listing 3-8

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.

The first step of converting the application to Android consists of typing the following:
buildozer init
Buildozer will create a file with the name buildozer.spec in the folder. Open this file using a text editor or IDLE. This file contains some characteristics for building your APK, and you are expected to change some parameters. However, if you take into account that the minimum Android version supported by Buildozer is 6, our advice is to change the following lines only. The row that reads:
title = My Application

can be replaced with a different application name without any problem.

The row that reads:
requirements = python3,kivy
should be changed to the following:
requirements = python3,kivy,numpy,pillow

We added numpy and pillow because, in the following chapters, we will need these libraries for image processing.

The row that reads:
source.include_exts = py,png,jpg,kv,atlas
should read as follows:
source.include_exts = py,png,jpg,kv,atlas,ttf

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.

To construct the APK, you need Internet access. To begin the conversion process, type the following:
buildozer -v android debug

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.

Once Buildozer finishes with the conversion process, provided no errors are encountered, you will find a new folder called bin in your working folder. Inside this folder, you will find your APK file. Copy this file to the Android device and install it. Alternatively, Buildozer can install the APK on your device by connecting it through USB to your computer. First, you need to enable the debug options on your Android device and type the following on the Terminal window of Ubuntu:
buildozer android deploy run logcat

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset