• Search in book...
• Toggle Font Controls
Â© 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_6

# 6. Stereoscopic 3D Programming

(1)
Leon, Guanajuato, Mexico
(2)
Queretaro, Mexico

In this chapter, we describe the basic programming tools needed to provide stereoscopic 3D views of the polygons.

## 6.1 Basics of a Stereoscopic Scene

To produce stereoscopic 3D views of polygons, we create two images of the three-dimensional objects. The first image is the projection of the three-dimensional polygons onto the screen, using the equations given in Chapter 4 and a point of projection with coordinates (VX1, VY1, VZ1). The second image uses a second point of projection with coordinates (VX2, VY2, VZ2), slightly shifted horizontally from the first. Note that VX2=VX1+âŠ—X, VY2=VY1, and VZ2=VZ1. Here, âŠ—X represents a small shift.

The two images obtained will not be displayed on the screen. Instead, we store them in a memory region to be binary ORed. The resulting image will appear on the computer's screen. Therefore, this process requires us to allocate three binary images in a memory region.

To appreciate the stereoscopic effect, you need to observe the scene through colored filters, red for the left eye and cyan for the right eye. For this purpose, you can construct glasses made with red and cyan cellophane films, which are commercially available, or you can acquire commercially available glasses specifically made for this purpose (often called 3D glasses). However, in both cases, we must ensure that each filter transmits only its corresponding color. In our experience, we had to add an extra film to the red filter. Additionally, we must take care of the properties of the cellophane films as we have found different transparency qualities among them.

The program will perform the required calculations and will be in charge of drawing the corresponding red and cyan images on the screen. Figure 6-1 shows a scene of the program running on an Android cell phone. As mentioned, you can see the stereoscopic effect only if you observe the scene through appropriate red-cyan filters.

## 6.2 Programming and ORing the Images

To calculate the binary OR between the left and right images, we have to import Pillow into our program, which is a specialized library for image processing. To verify if you have it installed on your computer, use the pip3 list directive . It should appear on the list. Otherwise, you can install it by typing the pip3 install Pillow command in your Terminal window.

We need three modules from Pillow, which will be available in the program by using the from PIL import Image, ImageDraw, ImageFont directive.

From the three libraries, Image will allow us to create the three images that this process requires. The second module, ImageDraw, provides tools for drawing lines. We will use straight lines to draw the edges of our polygons and to fill their faces. In our working example, we will fill only the back faces of our polygons. Finally, the last module, ImageFont, will allow us to display text.

Because the resulting image is a binary one, to be able to display it on the screen, we need to convert it into an appropriate image file. For our working example, we will use the png format. We will perform this conversion in a memory region. Therefore, we need to allocate memory and make it behave as a file. We will refer to a block of memory with this characteristic as an in-memory file. We will achieve this task using the library io, which we will incorporate into our program with the import io directive . Finally, the image stored in the in-memory file will be converted into a Kivy image to be displayed on the screen. We describe this process in detail in the following sections.

## 6.3 Projections

The program begins by creating the three required images by utilizing the Initialize(B, *args) function , which receives the handle B to access the widgets. Initialize(B, *args) uses the Image.new directive from PIL to create the three images, which we will name PilImage1, PilImage2, and PilImage3. The first image, PilImage1, will store the red image, while PilImage2 will store the cyan image. PilImage3 will store the result of the bitwise OR operation.

Additionally, to draw lines, we need to create two drawing instances, which we will name Draw1 and Draw2. These instances also allow us to display text.

We want to display a rounded frame like the one depicted in Figure 6-2, so the dimensions of our PIL images will be reduced by ten pixels in width and height compared to the Kivy image declared in the file.

When the program initiates, or when Button2 is pressed, the 3D Images text is displayed, as shown in Figure 6-2. To display text on the screen, we have to select one of the TrueType fonts available in Ubuntu, in the computer/usr/share/fonts/truetype folder. In this folder, we have several subfolders with names of the TrueType fonts. Copy the appropriate ttf file into your working folder. For our programs, we have selected Gargi.ttf.
The code that corresponds to the Initialize(B, *args) function is shown in Listing 6-1.
def Initialize(B, *args):
global W,H, XC,YC;
global PilImage1,PilImage2, Draw1,Draw2;
W,H=B.ids.Screen1.size;
XC=int (W/2)
YC=int(H/2)
PilImage1= Image.new('RGB', (W-10, H-10),
(60, 70, 30));
Draw1 = ImageDraw.Draw(PilImage1);
PilImage2= Image.new('RGB', (W-10, H-10),
(60, 70, 30));
Draw2 = ImageDraw.Draw(PilImage2);
Font = ImageFont.truetype('Gargi.ttf', 70)
Draw1.text( (30,200), "3D Images",
fill =(255,0,0,1), font=Font);
Draw1.text( (50,200), "3D Images",
fill =(0,255,255,1), font=Font);
ShowScene(B);
Listing 6-1

The Initialize(B, *args) Function

After creating the left and right images with their respective texts, Initialize(B, *args) calls the ShowScene(B) function . This function receives handle B to access the widgets declared in File.kv and performs the following tasks.
1. 1.

ShowScene(B) creates two NumPy arrays, called Array1 and Array2, resulting from the conversion of the data stored in PilImage1 and PilImage2.

2. 2.

An additional array, named Array3, is created as a result of bitwise ORing the elements of Array1 with Array2.

3. 3.

PilImage3 is created after converting Array3 into image data.

4. 4.

At this point, PilImage3 is converted into a Kivy image, following the procedure given at

The procedure is as follows.
1. a.

A region of memory, behaving as a file, is allocated using the Memory=io.BytesIO() directive. This directive provides a memory region named Memory that can be used as a file. Now we can store our image in this memory block by means of the PilImage3.save(Memory, ext="png") directive.

2. b.

As Memory behaves like a file, it has an inherent file pointer. Each time we access it for writing or reading, this file pointer is updated. Therefore, before copying the image to the Kivy widget, it is necessary to reload the file pointer to the beginning of the file, where our data will be stored. This is accomplished by using the Memory.seek(0) directive.

5. 5.

We now proceed to convert the image into the png format by using the ImagePNG = CoreImage(Memory, ext="png") directive. Now, ImagePNG holds our result.

6. 6.

The final step consists of copying ImagePNG to the Kivy image widget that is declared in the File.kv file. As ImagePNG has the appropriate format, it will be displayed on the screen simply by using the B.ids.Screen1.texture = ImagePNG.texture directive.

Before ShowScene(B) finishes, it must free the regions of memory that were dynamically allocated to accomplish this process. This is done by means of the directives shown in Listing 6-2.
ImagePNG.remove_from_cache();
Memory.close();
PilImage3.close();
Array1=None;
Array2=None;
Array3=None.
Listing 6-2

Freeing Previously Allocated Resources

The code for ShowScene(B) is shown in Listing 6-3.
def ShowScene(B):
Array1=np.array(PilImage1);
Array2=np.array(PilImage2);
Array3=Array1 | Array2;
PilImage3=Image.fromarray(Array3);
Memory=io.BytesIO();
PilImage3.save(Memory, format="png");
Memory.seek(0);
ImagePNG=CoreImage(Memory, ext="png");
B.ids.Screen1.texture=ImagePNG.texture;
ImagePNG.remove_from_cache()
Memory.close();
PilImage3.close();
Array1=None;
Array2=None;
Array3=None;
Listing 6-3

Code for the ShowScene(B) Function

As our program allows the polygons to be rotated in real-time, we have to clean the previous scene before exhibiting the new one. The ClearObjects() function performs this task. This function draws in Draw1 and Draw2 rectangles with the same sizes as PilImage1 and PilImage2 using the background colors that we previously selected. As a result, the scene will appear to have been cleaned. The code for ClearObjects() is given in Listing 6-4.
def ClearObjects():
Draw1.rectangle( (0, 0, H-10, W-10),
fill=(60, 70, 30, 1) );
Draw2.rectangle( (0, 0, H-10, W-10),
fill=(60, 70, 30, 1) );
Listing 6-4

Function to Clear the Screen

## 6.4 Polygon Structure

To place the polygon in a stereoscopic scene, you can use the same polygon structure described in Chapter 5.

A stereoscopic scene provides depth perception, which means that small changes in the Z-coordinate can cause changes in the polygon position that can drive it off-screen. Therefore, it is convenient to devise a method to help position the polygons.

To visualize this situation, letâ€™s consider a point of projection VX1 situated on a plane of projection at a distance D from the screen, as depicted in Figure 6-3. The point X on the screen is the projection of the red point. We obtain point X by drawing a straight line that goes from VX1 through the red point up to the screen.

In Figure 6-3, the Z-axis passes through the center of the screen and the projected point, X, is shifted from this center. This method consists in calculating this shift. This quantity, calculated using properties of similar triangles, corresponds to VX1. An analog situation occurs for the y-direction. We will shift the whole scene using this value.

Before shifting the scene, we have to recall the equations for projecting a three-dimensional point given by Equations (5.â€‹1) and (5.â€‹2). Additionally, we have to rewrite these equations appropriately because the origin (0,0) of a PIL image is in the upper-left corner, in contrast to a Kivy image in which (0,0) corresponds to the bottom-left corner. Therefore, we have to maintain Equation (5.â€‹1) unaltered but, in Equation (5.â€‹2), the values added to YC should now be subtracted instead. The equations that correspond to Equations (5.â€‹1) and (5.â€‹2) for a PIL image read as follows:
(6.1)
(6.2)
Examining Equations (6.1) and (6.2) suggests the following parameter:
(6.3)
Now, as the red point depicted in Figure 6-3 is at , substituting this value in Equation (6.3) allows us to define the following constant factor:
(6.4)

In Equation (6.4), Factor0 âˆ— VX1 will shift the projection of the red point, or equivalently, the point (0,0,0) precisely to the center of the screen.

Now, the Z distance is measured from the screen up to the plane of projection, as depicted in Figure 6-4. Therefore, Z=0 corresponds to points at the screen plane, while Z=D corresponds to points at the plane of projection. To proceed further, letâ€™s define a percentage value between 0 and 1, denoted as P. Then, we can position points at any distance between the screen and the plane of projection by using the formula Z=P*D. Letâ€™s focus on one of these points, depicted in blue in Figure 6-4.
As indicated in Figure 6-4, the blue point is located at a distance Z= P*D, measured from the screen. Using triangle properties:
(6.5)
From Equation (6.5), the required shift h is given as follows:
(6.6)

Equation (6.6) specifies that, when the blue point is positioned at a vertical height h and at a distance Z=P*D from the screen, it will give the same projection X as the red point. The complete scene will be shifted by an amount equal to Factor0 âˆ— VX1, so the red point and all the points satisfying Equation (6.6) will be projected at the center of the screen.

From this description, we can now describe our method for positioning the polygons at distances Z=P*D from the screen. The method consists of positioning the polygons first at the center of the screen using Equation (6.6), and then shifting them to a desired position.

Equation (6.6) does not depend on the parameter D, which means we can experiment with different stereoscopic perspectives using different D values, without the drawback of losing the polygons from the scene.

We can now proceed to program the polygons based on this description. We will first set the coordinates of the points of projection and the value of distance D. For the working example, we chose the following values.
D=8000;
VX1=1000; VY1=1000; VZ1=0;
VX2=970; VY2=1000; VZ2=0;
Factor0 =(D-VZ1) / (D/2-VZ1);
P=0.65;
Next, we provide the polygon data. For this example, the cube polygon has edges with a length equal to 15 pixels, placed at a distance of 0.65*D pixels from the screen, and centered at (30, 40). The corresponding code is shown in Listing 6-5.
P=0.65;
LX=15; LY=15; LZ=15; # Cube dimensions
#Offsets from the origin.
#OffZ is measured starting from the screen
OffX=(2*P-1)*VX1+30; OffY=(2*P-1)*VY1+40; OffZ=P*D;
Cube=[
5,   #Number of vertexes +1
#Front face
[ LX+OffX,  LY+OffY,  LZ+OffZ],    #Vertex 1
[ LX+OffX, -LY+OffY,  LZ+OffZ],    #Vertex 2
[-LX+OffX, -LY+OffY,  LZ+OffZ],    #Vertex 3
[-LX+OffX,  LY+OffY,  LZ+OffZ],    #Vertex 4
[ LX+OffX,  LY+OffY,  LZ+OffZ],    #Vertex 1
#Back face
[ LX+OffX,  LY+OffY,  -LZ+OffZ],    #Vertex 1
[ LX+OffX, -LY+OffY,  -LZ+OffZ],    #Vertex 2
[-LX+OffX, -LY+OffY,   -LZ+OffZ],   #Vertex 3
[-LX+OffX,  LY+OffY,   -LZ+OffZ],   #Vertex 4
[ LX+OffX,  LY+OffY,   -LZ+OffZ],   #Vertex 1
##Red, green, blue color. Entries must be
#between 0 and 1. Entry = 2*Num+1
[0,0,1],
#Rotation Center RC; Entry = 2*Num+2
[OffX,OffY,OffZ]
];
Listing 6-5

Filling the Cube Polygon

Although they are not used here, we maintain the polygon colors in the 2*Num+1 place of the structure, as in Chapter 5, for compatibility purposes.

## 6.5 DrawAxes Function

The DrawAxes (P, VX, VY ,VZ, Which) function is in charge of drawing the three-dimensional axes on the screen. This function receives a parameter P, corresponding to a polygon structure, and the three coordinates of a point of projection, (VX, VY, VZ). The parameter Which is 0 for the red projection and 1 for the cyan projection. In our program, VZ is always set to 0. This variable may be used in future applications. As described, the shifting values Factor0*VX and Factor0*VY are used to position the three-dimensional origin (0,0,0) at the center of the screen.

The code for DrawAxes(VX,VY,VZ,Which) is shown in Listing 6-6.
def DrawAxes(VX,VY,VZ,Which):
global  Draw1,Draw2,Factor0;
Length=60;
if (Which==0):
r,g,b = 255, 0, 0; #red Image
Draw=Draw1;
else:
r,g,b = 0, 255, 255; #blue image
Draw=Draw2;
Z0=D/2; #Origin of the three-dimensional axis
Factor=(D-VZ)/(D-Z0-VZ);
x1=XC+Factor*(0-VX)+Factor0*VX;
y1=YC-Factor*(0-VY)-Factor0*VY;
x2=XC+Factor*(Length-VX)+Factor0*VX;
y2=YC-Factor*(0-VY)-Factor0*VY;
#Drawing axis x
Draw.line( (x1,y1,x2,y2),fill=(r,g,b),width=3 );
x2=XC+Factor*(0-VX)+Factor0*VX;
y2=YC-Factor*(Length-VY)-Factor0*VY;
#Drawing axis y
Draw.line( (x1,y1,x2,y2),fill=(r,g,b),width=3 );
Z0=Z0+Length;
Factor=(D-VZ)/(D-Z0-VZ);
x2=XC+Factor*(0-VX)+Factor0*VX;
y2=YC-Factor*(0-VY)-Factor0*VY;
#Drawing axis z
Draw.line( (x1,y1,x2,y2),fill=(r,g,b),width=3 );
Listing 6-6

The DrawAxes(VX,VY,VZ,Which) Function

The DrawEdges(P, VX, VY, VZ, Which) and FillPolygon(P, VX, VY, VZ, Which) functions are similar to their respective functions described in Chapter 5, with the addition of the Which parameter.

## 6.6 Points of Projection

The points of projection for the red and cyan images have horizontal coordinates VX1 and VX2 and should differ slightly. In principle, increasing the difference between VX1 and VX2 produces a better stereoscopic appearance. However, there is a maximum difference allowed. It is advisable to experiment with different values.

Listing 6-7 shows the complete code for main.py and Listing 6-8 shows the complete code for File.kv.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics import Line, Color
from kivy.clock import Clock
from kivy.core.image import Image as CoreImage
from PIL import Image, ImageDraw, ImageFont
import io
import os
import numpy as np
from kivy.lang import Builder
os.path.join(os.path.dirname(os.path.abspath(
__file__)), 'File.kv')
);
#Avoid Form1 of being resizable
from kivy.config import Config
Config.set("graphics","resizable", False)
Config.set('graphics', 'width',  '480');
Config.set('graphics', 'height', '680');
#The width and height (W,H) and the
#center (XC,YC) of canvas are set by the
#function Initialize() after
#Clock.schedule_once has been executed.
Flag=False;
NUMBER=0;  #Polygon number
#============== 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 as the last one.
#The next pair of entries corresponds to
#the center of rotation in screen coordinates
#The triplet before the end of the structure
#corresponds to the polygon color, red, green, blue.
D=8000;
VX1=1000; VY1=1000; VZ1=0;
VX2=970; VY2=1000; VZ2=0;
Factor0 =(D-VZ1) / (D/2-VZ1);
P=0.65;
LX=15; LY=15; LZ=15; # Cube dimensions
#Offsets from the origin.
#OffZ is measured starting from the screen
OffX=(2*P-1)*VX1+30; OffY=(2*P-1)*VY1+40; OffZ=P*D;
Cube=[
5,   #Number of vertexes +1
#Front face
[ LX+OffX,  LY+OffY,  LZ+OffZ],    #Vertex 1
[ LX+OffX, -LY+OffY,  LZ+OffZ],    #Vertex 2
[-LX+OffX, -LY+OffY,  LZ+OffZ],    #Vertex 3
[-LX+OffX,  LY+OffY,  LZ+OffZ],    #Vertex 4
[ LX+OffX,  LY+OffY,  LZ+OffZ],    #Vertex 1
#Back face
[ LX+OffX,  LY+OffY,  -LZ+OffZ],    #Vertex 1
[ LX+OffX, -LY+OffY,  -LZ+OffZ],    #Vertex 2
[-LX+OffX, -LY+OffY,   -LZ+OffZ],   #Vertex 3
[-LX+OffX,  LY+OffY,   -LZ+OffZ],   #Vertex 4
[ LX+OffX,  LY+OffY,   -LZ+OffZ],   #Vertex 1
##Red, green, blue color. Entries must be
#between 0 and 1. Entry = 2*Num+1
[0,0,1],
#Rotation Center RC; Entry = 2*Num+2
[OffX,OffY,OffZ]
];
P=0.0;
L=80; LZ=20;  # Pentagon dimensions
#Offsets from the origin. OffZ measured starting at the screen
OffX=(2*P-1)*VX1-90; OffY=(2*P-1)*VY1-110; OffZ=P*D;
Pentagon=[
6,  #Number of vertexes +1
#Front face
#Vertex 1
[ 1*L+OffX,       0*L+OffY,     LZ+OffZ],
#Vertex 2
[ 0.309*L+OffX,  0.95*L+OffY,    LZ+OffZ],
#Vertex 3
[ -0.809*L+OffX,  0.58*L+OffY,  LZ+OffZ],
#Vertex 4
[ -0.809*L+OffX,  -0.58*L+OffY, LZ+OffZ],
#Vertex 5
[ 0.309*L+OffX,  -0.95*L+OffY,  LZ+OffZ],
#Vertex 1, repeated
[ 1*L+OffX,       0*L+OffY,     LZ+OffZ],
#Back face
#Vertex 1
[ 1*L+OffX,       0*L+OffY,     -LZ+OffZ],
#Vertex 2
[ 0.309*L+OffX,  0.95*L+OffY,    -LZ+OffZ],
#Vertex 3
[ -0.809*L+OffX,  0.58*L+OffY,  -LZ+OffZ],
#Vertex 4
[ -0.809*L+OffX,  -0.58*L+OffY, -LZ+OffZ],
#Vertex 5
[ 0.309*L+OffX,  -0.95*L+OffY,  -LZ+OffZ],
#Vertex 1, repeated
[ 1*L+OffX,       0*L+OffY,     -LZ+OffZ],
#Red, green, blue color. Entries must
#be between 0 and 1. Entry = 2*Num+1
[1,0,0],
#Rotation Center RC, Entry = 2*Num+2
[OffX,OffY,OffZ]
];
#---------------------------------------------------
def DrawAxes(VX,VY,VZ,Which):
global  Draw1,Draw2,Factor0;
Length=60;
if (Which==0):
r,g,b = 255, 0, 0; #red Image
Draw=Draw1;
else:
r,g,b = 0, 255, 255; #blue image
Draw=Draw2;
Z0=D/2; #Origin of the three-dimensional axis
Factor=(D-VZ)/(D-Z0-VZ);
x1=XC+Factor*(0-VX)+Factor0*VX;
y1=YC-Factor*(0-VY)-Factor0*VY;
x2=XC+Factor*(Length-VX)+Factor0*VX;
y2=YC-Factor*(0-VY)-Factor0*VY;
#Drawing axis x
Draw.line( (x1,y1,x2,y2),fill=(r,g,b),width=3 );
x2=XC+Factor*(0-VX)+Factor0*VX;
y2=YC-Factor*(Length-VY)-Factor0*VY;
#Drawing axis y
Draw.line( (x1,y1,x2,y2),fill=(r,g,b),width=3 );
Z0=Z0+Length;
Factor=(D-VZ)/(D-Z0-VZ);
x2=XC+Factor*(0-VX)+Factor0*VX;
y2=YC-Factor*(0-VY)-Factor0*VY;
#Drawing axis z
Draw.line( (x1,y1,x2,y2),fill=(r,g,b),width=3 );
def DrawEdges(P, VX, VY, VZ, Which):
Num=P[0]; #Reading number of face edges +1
if (Which==0):
r,g,b =255, 0, 0; #red Image
Draw=Draw1
else:
r,g,b = 0, 255 ,255;  #cyan image
Draw=Draw2
for n in range(1,Num): #Drawing front edges
Factor=(D-VZ)/(D-P[n][2]-VZ);
x1=XC+Factor*(P[n][0]-VX)+Factor0*VX;
y1=YC-Factor*(P[n][1]-VY)-Factor0*VY;
Factor=(D-VZ)/(D-P[n+1][2]-VZ);
x2=XC+Factor*(P[n+1][0]-VX)+Factor0*VX;
y2=YC-Factor*(P[n+1][1]-VY)-Factor0*VY;
Draw.line( (x1,y1,x2,y2),fill=(r,g,b),
width=5 );
for n in range(Num+1,2*Num): #Drawing back edges
Factor=(D-VZ)/(D-P[n][2]-VZ);
x1=XC+Factor*(P[n][0]-VX)+Factor0*VX;
y1=YC-Factor*(P[n][1]-VY)-Factor0*VY;
Factor=(D-VZ)/(D-P[n+1][2]-VZ);
x2=XC+Factor*(P[n+1][0]-VX)+Factor0*VX;
y2=YC-Factor*(P[n+1][1]-VY)-Factor0*VY;
Draw.line( (x1,y1,x2,y2),fill=(r,g,b),
width=5 );
#Drawing edges between back and front faces
for n in range(1,Num):
Factor=(D-VZ)/(D-P[n][2]-VZ);
x1=XC+Factor*(P[n][0]-VX)+Factor0*VX;
y1=YC-Factor*(P[n][1]-VY)-Factor0*VY;
Factor=(D-VZ)/(D-P[Num+n][2]-VZ);
x2=XC+Factor*(P[Num+n][0]-VX)+Factor0*VX;
y2=YC-Factor*(P[Num+n][1]-VY)-Factor0*VY;
Draw.line( (x1,y1,x2,y2),  fill=(r,g,b),
width=5 );
#---------------------------------------------------
def Rotate(P, Sense):
global p,q;
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];
# Center of rotation
RCp=P[2*Num+2][p]; RCq=P[2*Num+2][q];
#Rotating front face
for n in range(1,Num+1):
Xp=(P[n][p]-RCp)*Cos_Teta
+(P[n][q]-RCq)*Sin_Teta +RCp;
Xq=-(P[n][p]-RCp)*Sin_Teta
+(P[n][q]-RCq)*Cos_Teta +RCq;
P[n][p]=Xp;
P[n][q]=Xq;
#Rotating Back face
for n in range(Num+1,2*Num+1):
Xp=(P[n][p]-RCp)*Cos_Teta
+  (P[n][q]-RCq)*Sin_Teta +RCp;
Xq=-(P[n][p]-RCp)*Sin_Teta
+  (P[n][q]-RCq)*Cos_Teta +RCq;
P[n][p]=Xp;
P[n][q]=Xq;
#---------------------------------------------------
#Function for filling the back polygon
#face, line by line
#Array to store intersection points
Intersect=np.zeros(30);
def FillPolygon(P, VX,VY,VZ,Which):
Num=P[0]; #Reading number of face edges +1
if (Which==0):
r,g,b = 200, 0, 0; #red Image
Draw=Draw1
else:
r,g,b= 0, 200, 200; #cyan image
Draw=Draw2
#Calculating polygon YMin, Ymax for
#limiting number of line scans
Factor=(D-VZ)/(D-P[Num+1][2]-VZ);
YMin=YC-Factor*(P[Num+1][1]-VY)-VY;
YMax=YMin;
for n in range(Num+1,2*Num):
Factor=(D-VZ)/(D-P[n+1][2]-VZ);
if YMin>YC-Factor*(P[n+1][1]-VY)-Factor0*VY:
YMin=YC-Factor*(P[n+1][1]-VY)-Factor0*VY;
if YMax<YC-Factor*(P[n+1][1]-VY)-Factor0*VY:
YMax=YC-Factor*(P[n+1][1]-VY)-Factor0*VY;
#We have now (YMin, YMax)
#We now proceed filling lines
#between Ymin and Ymax
for y in np.arange (YMin, YMax, 2):
Counter=0;
#We will search line cuts segment by segment
for n in range(Num+1, 2*Num):
# We first order the two vertices of
#each segment such that Y1<Y2
Factor1=(D-VZ)/(D-P[n][2]-VZ);
YA=YC-Factor1*(P[n][1]-VY)-Factor0*VY;
Factor2=(D-VZ)/(D-P[n+1][2]-VZ);
YB=YC-Factor2*(P[n+1][1]-VY)-Factor0*VY;
if ( YA<YB ):
Y1=YA;
X1=XC+Factor1*(P[n][0]-VX)
+Factor0*VX;
Y2=YB;
X2=XC+Factor2*(P[n+1][0]-VX)
+Factor0*VX;
else:
Y1=YB;
X1=XC+Factor2*(P[n+1][0]-VX)
+Factor0*VX;
Y2=YA;
X2=XC+Factor1*(P[n][0]-VX)
+Factor0*VX ;
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 M a large value
M=1.0e8;
#We store the x value
Intersect[Counter]=(y-Y1)*M+X1;
# And we increment Counter as a new
#value has being stored
Counter=Counter+1;
# Geometrical closed figure.
#Only even number of cuts allowed
if(Counter>1 and Counter %2 ==0):
#Intersect1 contains ordered
#pairs of x values
Intersect1=np.sort(Intersect[0:Counter]);
#We now trace lines between
#pairs of intersections
for n in range(0,Counter,2):
XIntersect1=int(Intersect1[n]);
XIntersect2=int(Intersect1[n+1]);
Y=int(y)
#Drawing an horizontal line
#between x pairs
Draw.line( (XIntersect1,
Y,XIntersect2,Y),
fill=(r,g,b), width=1 );
#---------------------------------------------------
def ShowScene(B):
Array1=np.array(PilImage1);
Array2=np.array(PilImage2);
Array3=Array1 | Array2;
PilImage3=Image.fromarray(Array3);
Memory=io.BytesIO();
PilImage3.save(Memory, format="png");
Memory.seek(0);
ImagePNG=CoreImage(Memory, ext="png");
B.ids.Screen1.texture=ImagePNG.texture;
ImagePNG.remove_from_cache()
Memory.close();
PilImage3.close();
Array1=None;
Array2=None;
Array3=None;
#---------------------------------------------------
def ClearObjects():
Draw1.rectangle( (0, 0, H-10, W-10),
fill=(60, 70, 30, 1) );
Draw2.rectangle( (0, 0, H-10, W-10),
fill=(60, 70, 30, 1) );
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.1);
def Initialize(B, *args):
global W,H, XC,YC;
global PilImage1,PilImage2, Draw1,Draw2;
W,H=B.ids.Screen1.size;
XC=int (W/2)
YC=int(H/2)
PilImage1= Image.new('RGB', (W-10, H-10),
(60, 70, 30));
Draw1 = ImageDraw.Draw(PilImage1);
PilImage2= Image.new('RGB', (W-10, H-10),
(60, 70, 30));
Draw2 = ImageDraw.Draw(PilImage2);
Font = ImageFont.truetype('Gargi.ttf', 70)
Draw1.text( (30,200), "3D Images",
fill =(255,0,0,1), font=Font);
Draw1.text( (50,200), "3D Images",
fill =(0,255,255,1), font=Font);
ShowScene(B);
def Temporal(B, *args):
global Flag, NUMBER, p, q;
if (Flag==True):
if (B.ids.Button3.state=="down"):
Sense=-1; p=0; q=1;
if(B.ids.Button5.state=="down"):
Sense=-1; p=0;q=2;
if(B.ids.Button7.state=="down"):
Sense=-1; p=1;q=2;
if(B.ids.Button4.state=="down"):
Sense=1; p=0;q=1;
if(B.ids.Button6.state=="down"):
Sense=1;p=0;q=2;
if(B.ids.Button8.state=="down"):
Sense=1;p=1;q=2;
if(NUMBER==0):
Rotate(Cube, Sense);
if (NUMBER==1):
Rotate(Pentagon, Sense);
#  Clearing Draw1 and Draw2
ClearObjects();
DrawAxes(VX1,VY1,VZ1,0);
DrawAxes(VX2,VY2,VZ2,1);
FillPolygon(Cube,VX1,VY1,VZ1,0);
DrawEdges(Cube,VX1,VY1,VZ1,0); #red cube
FillPolygon(Cube,VX2,VY2,VZ2,1);
DrawEdges(Cube,VX2,VY2,VZ2,1); #cyan cube
#red pentagon
FillPolygon(Pentagon,VX1,VY1,VZ1,0);
DrawEdges(Pentagon,VX1,VY1,VZ1,0);
#cyan pentagon
FillPolygon(Pentagon,VX2,VY2,VZ2,1);
DrawEdges(Pentagon,VX2,VY2,VZ2,1);
ShowScene(B);
def Button1_Click(B):
global Draw1, Draw2, PilImage1, PilImage2;
#Clearing Draw1 and Draw2
ClearObjects();
DrawAxes(VX1,VY1,VZ1,0);
DrawAxes(VX2,VY2,VZ2,1);
FillPolygon(Cube,VX1,VY1,VZ1,0);
DrawEdges(Cube,VX1,VY1,VZ1,0); #red cube
FillPolygon(Cube,VX2,VY2,VZ2,1);
DrawEdges(Cube,VX2,VY2,VZ2,1); #cyan cube
#red pentagon
FillPolygon(Pentagon,VX1,VY1,VZ1,0);
DrawEdges(Pentagon,VX1,VY1,VZ1,0);
#cyan pentagon
FillPolygon(Pentagon,VX2,VY2,VZ2,1);
DrawEdges(Pentagon,VX2,VY2,VZ2,1);
ShowScene(B);
def Button2_Click(B):
#  Clearing Draw1 and Draw2
ClearObjects();
Font = ImageFont.truetype('Gargi.ttf', 70)
Draw1.text( (30,200), "3D Images",
fill =(255,0,0,1), font=Font);
Draw1.text( (50,200), "3D Images",
fill =(0,255,255,1), font=Font);
ShowScene(B);
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;
def Button5_Click(B):
global Flag;
Flag=True;
def Button5_Release(B):
global Flag;
Flag=False;
def Button6_Click(B):
global Flag;
Flag=True;
def Button6_Release(B):
global Flag;
Flag=False;
def Button7_Click(B):
global Flag ;
Flag=True;
def Button7_Release(B):
global Flag;
Flag=False;
def Button8_Click(B):
global Flag;
Flag=True;
def Button8_Release(B):
global Flag;
Flag=False;
def Button9_Click(B):
global NUMBER;
NUMBER=(NUMBER+1)%2;
B.ids.Label1.text=str(NUMBER);
# This is the Start Up code.
class StartUp (App):
def build (BU):
BU.title="Form1"
return Form1();
if __name__ =="__main__":
StartUp().run();
Listing 6-7

Code Listing for main.py

#:set W 440
#:set H 440
<Form1>:
id : Form1
Image:
id: Screen1
size_hint: None,None
pos_hint: {"x":0.04, "y":0.34}
size: W,H
canvas.before:
Color:
rgba: 0.8 ,0.8, 0.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.03}
size: 100,30
Button:
id: Button2
on_press: Form1.Button2_Click()
text: "Button2"
size_hint: None,None
pos_hint: {"x": 0.63, "y":0.03}
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.12}
size: 100,30
always_release: True
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.12}
size: 100,30
Button:
id: Button5
on_press: Form1.Button5_Click()
on_release:  Form1.Button5_Release()
text: "Button5"
size_hint: None,None
pos_hint: {"x": 0.05, "y":0.20}
size: 100,30
Button:
id: Button6
on_press: Form1.Button6_Click()
on_release:  Form1.Button6_Release()
text: "Button6"
size_hint: None,None
pos_hint: {"x": 0.73, "y":0.20}
size: 100,30
Button:
id: Button7
on_press: Form1.Button7_Click()
on_release:  Form1.Button7_Release()
text: "Button7"
size_hint: None,None
pos_hint: {"x": 0.05, "y":0.28}
size: 100,30
Button:
id: Button8
on_press: Form1.Button8_Click()
on_release:  Form1.Button8_Release()
text: "Button8"
size_hint: None,None
pos_hint: {"x": 0.73, "y":0.28}
size: 100,30
Button:
id: Button9
on_press: Form1.Button9_Click()
text: "Button9"
size_hint: None,None
pos_hint: {"x": 0.38, "y":0.12}
size: 100,30
Label:
id: Label1
text: "0"
font_size: 30
color: 1,1,0,1
size_hint: None,None
pos_hint: {"x": 0.38, "y":0.20}
size: 100,30
Listing 6-8

Code Listing for File.kv

## 6.7 Summary

In this chapter, we programmed the polygons in 3D stereoscopic scenes. We placed and rotated them using the three-dimensional projection equations derived in previous chapters. We created two images using Pillow and performed the logical OR between them using NumPy. Finally, the resulting ORed image was converted to a Kivy image and displayed on the screen.

• No Comment
..................Content has been hidden....................