Plotting 4D Graphs With QOpenGLWidget

A 4D graph can be used for plotting a graph of a complex function, with complex numbers as its input and output. A complex number can be represented as a pair of two real numbers:

  • A real part and an imaginary part.
  • A magnitude and an argument.

An image has a width and a height, but you can add an illusion of depth using a linear transform and changes in hue. A common way to add the fourth dimension is to use colors to represent its value. If you use a color for the fourth dimension, you want to add a legend to tell the viewer how to estimate the value of the fourth coordinate.

A good example of one such graph is the graph of the Lambert function found in the Wikipedia.

The Lambert W function is defined as follows:

x=ye^y\Rightarrow y=W_n(x)

where n \in \mathbb{Z} is the branch number.

In this post, I will discuss the use of Qt5 modules in creating my version of the graph:

The graph is not perfect and complete, but in this post I will explain how to create the components of the graph.

Combining OpenGL and 2D Painting

In Qt5, the OpenGL widget is a painting device like any other widget. So, you can use a painter of class QPainter to paint 2D shapes on it. The painter is useful if you want to add text to an image. Remember to unbind every GL object (vertex array objects, buffers, textures) before painting with the painter.

Before painting with the GL functions, call the painter’s method beginNativePainting. After finishing the use of GL functions, call endNativePainting

If you want the text rotation, you can use SVG. A simple way to add SVG text is using the QSvgRenderer class, that can create an SVG image from a file or a string.

Single Points

You can draw single points in OpenGL. To do so, you must enable GL_PROGRAM_POINT_SIZE using glEnable in your calling program, and define glPointSize in the vertex shader.

Matrices

The main types of matrices used in my example are:

  • Frustum – A perspective matrix defined using the minimum and maximum values of coordinates.
  • Viewport – a matrix that transforms the coordinates used by GL into the coordinates of the input rectangle. That transform will be used for placing the axis ticks snd texts.

The Program

The main window is a Qt Widget, so

The QMake File: qmake.pro:

The following definition will be used for creating the make file.

TARGET=executable
SOURCES=main.cpp
HEADERS=graph.h
HEADERS+=shaders.h
HEADERS+=svg.h
  
DESTDIR=bin
QT=core
QT+=gui
QT+=svg

greaterThan(QT_MAJOR_VERSION, 4): QT+=widgets

The main Header File

The header files ‘graph.h’ contains some definition to be used by the main program and the widget extending the QT Widget:

#include <QtWidgets/QOpenGLWidget>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QPainter>
#include <QtSvg>
#include <QStyleOptionGraphicsItem>

struct graph_point {
  GLfloat coords[3];
  GLfloat hue;
};

struct vertex2D {
  GLfloat coords[2];
  GLfloat hue;
};

struct axis_ticks {
  GLfloat rect_coords[3];
  GLfloat texture_coords[2];
};

class GraphWidget:public QOpenGLWidget{
private:
  QOpenGLVertexArrayObject m_vao;
  QOpenGLBuffer m_gradient_vbo;
  QOpenGLBuffer m_point_vbo;
  QOpenGLBuffer m_ibo;
  
  QOpenGLShader *m_vertexShader;
  QOpenGLShader *m_fragmentShader;
  QOpenGLShaderProgram *m_program;
  QPainter m_painter;
  QFont m_font;
  int fontSize;
  int m_width, m_height;
  float legend_left;
  QMatrix4x4 transform;
  QMatrix4x4 viewport;

  void addLegend(void);
  void populatePointBuffer(void);
  void paintGraph(void);
  void draw3DLine(QVector4D fromVec, QVector4D toVec);
  void addAxes(void);
  void add_x_ticks(void);
  void add_y_ticks(void);
  void add_z_ticks(void);
  void add_z_text(void);
  
public:
  GraphWidget(QWidget *parent=nullptr);
  ~GraphWidget();
  void initializeGL();
  void paintGL();
  void resizeGL(int w, int h);
};

HSV to RGB

The fourth coordinate of our graph represents arg(W(z)), an angle. In the graph we’ll use HSV (Hue, Saturation, Value) because the hue is given by the value of an angle. A shader-language function to covert from HSV to RGB is defined in file “shaders.h“:

#define TO_RGB_FUNC					\
  "#version 140\n"					\
  "vec4 to_rgb(float hue){ \n"				\
  "float s=1.0;\n"					\
  "float v=1.0;\n"					\
  "float c=v*s;\n"					\
  "float   x=c*(1-abs(mod(3*hue,2)-1));\n"		\
  "float   m=v-c;\n"					\
  "vec3  tempRGB=hue< -2./3?  vec3(0,x,c):\n"		\
  "              hue< -1./3?  vec3(x,0,c): \n"		\
  "              hue<     0?  vec3(c,0,x): \n"		\
  "              hue<  1./3?  vec3(c,x,0): \n"		\
  "              hue<  2./3?  vec3(x,c,0): \n"		\
  "		              vec3(0,c,x); \n"		\
  "\n"							\
  "return vec4(tempRGB+vec3(m,m,m),1);\n"		\
  "}\n"

The shader fragment for both the graph points and the legend is defined as follows:

#define FRAGMENT_SHADER				\
  TO_RGB_FUNC					\
  "varying float f_hue;\n"			\
  "void main(void){\n"				\
  "    gl_FragColor=to_rgb(f_hue);\n"		\
  "}\n"

Main Program Includes and Definitions

#include <stdio.h>

#include <graph.h>
#include <QApplication>
#include "shaders.h"
#include "svg.h"
#include <complex>
#include <iostream>


#define MIN_WIDTH 640
#define MIN_HEIGHT 480

using namespace std;

Constructor and Destructor

GraphWidget::GraphWidget(QWidget *parent):
  QOpenGLWidget(parent),
  m_vao(),
  m_gradient_vbo(),
  m_point_vbo(),
  m_ibo(QOpenGLBuffer::IndexBuffer),
  m_font()
{
  setGeometry(0,0,MIN_WIDTH,MIN_HEIGHT);
  setMinimumWidth(MIN_WIDTH);
  setMinimumHeight(MIN_HEIGHT);
}

GraphWidget::~GraphWidget(){
  makeCurrent();
  m_point_vbo.destroy();
  m_gradient_vbo.destroy();
  m_ibo.destroy();
  m_vao.destroy();
  doneCurrent();
  
}

// NUM_ARGS - Args of complex numbers.
// NUM_MAGNS - Number of magnitudes.
#define NUM_ARGS 140
#define NUM_MAGNS 200

Initializing, Populating the Point Buffer

Here we’ll define the variables that are initialize once. The following function, initializeGL, is called once upon the widget’s initization:

void GraphWidget::initializeGL(){
  m_vertexShader=new QOpenGLShader(QOpenGLShader::Vertex);
  m_fragmentShader=new QOpenGLShader(QOpenGLShader::Fragment);
  makeCurrent();
  glEnable(GL_BLEND);
  glEnable(GL_DEPTH_TEST);
  glEnable( GL_PROGRAM_POINT_SIZE );
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  m_vao.create();

  m_gradient_vbo.create();
  m_gradient_vbo.bind();
  m_gradient_vbo.allocate(4*sizeof(vertex2D));
  
  m_point_vbo.create();
  m_point_vbo.bind();
  m_point_vbo.allocate(NUM_MAGNS*NUM_ARGS*sizeof(graph_point));
  populatePointBuffer();

  GLshort indices[6]={0,1,2,2,3,0};
  m_ibo.create();
  m_ibo.bind();
  m_ibo.allocate(indices,sizeof(indices));
  m_ibo.release();
  m_gradient_vbo.release();
  m_point_vbo.release();
  m_program=new QOpenGLShaderProgram();
}

A call to glEnable( GL_PROGRAM_POINT_SIZE ); enables drawing single points.

The function populatePointBuffer uses the method ‘write‘ of class QOpenGLBuffer to write points to the vertex buffer. I use the buffer this way so I can use functions of complex variables.

void GraphWidget::populatePointBuffer(void){
  int vboOffset=0;
  graph_point vec[NUM_MAGNS];
  for (float magn=0;magn<1.4;magn+=1.4/NUM_MAGNS){
    int pos=0;
    for (float arg=-1;arg<1;arg+=2./NUM_ARGS){
      // The arg of W(Z) divided by pi.
      complex<float> iarg;
      iarg=complex<float>(0,M_PI*arg);
      complex<float> w=magn*exp(iarg);
      complex<float> z=w*exp(w);
      vec[pos].hue=arg;
      vec[pos].coords[0]=imag(z);
      vec[pos].coords[1]=magn;
      vec[pos].coords[2]=real(z);
      pos+=1;
    }
    m_point_vbo.write(vboOffset,vec,sizeof(vec));
    vboOffset+=sizeof(vec);
  }
}

Resizing

The function resizeGL is called as a response to a widget resize event. In this function the values that change due to a resize event are set. The properties set here are:

  • The font size for the painter
  • The transform matrix
  • The viewport matrix
void GraphWidget::resizeGL(int w, int h){
  m_width=w;
  m_height=h;
  fontSize=12.* min((float)m_width/MIN_WIDTH,(float)m_height/MIN_HEIGHT);
  m_font.setPixelSize(fontSize);
  float z_far=0.05;
  float z_near=0.01;
  float a=2./(m_height-2*fontSize);
  float b=1.8-a*m_height;
  float y_floor=b-2.*a*fontSize;
  
  // leftInPixels,RightInPixels - left and right boundaries
  //                              of the graph.
  float leftInPixels=12*fontSize+7;
  float rightInPixels=m_width - 12 - 15*fontSize;
  a=2/(rightInPixels-leftInPixels);
  b=-1-a*leftInPixels;
  float x_left=b;
  float x_right=a*m_width+b;
  
  transform.setToIdentity();

  transform.frustum(x_left,x_right,y_floor,1.8,z_near,z_far);
  transform.translate(0,0,-(z_far+z_near)/2);
  transform.scale(1,1,(z_far-z_near)/2);
  viewport.setToIdentity();
  viewport.viewport(rect());
}

Painting

The function paintGL is called as a response to paint events. Following is the code of paintGL:

void GraphWidget::paintGL(){
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  m_painter.begin(this);
  m_painter.setPen(QColor(255,255,255,255));
  m_painter.setFont(m_font);
  addLegend();
  addAxes();

  paintGraph();

  m_painter.end();
}

Let us look at the functions called by the paint event handler:

Add Legend

The function addLegend uses OpenGL function to create the HSV gradient and the painter to add ticks and text values:

void GraphWidget::addLegend(void){
  QString str="arg(W(Z))";

  // Drawing the header

  QRect boundingRect=m_painter.boundingRect(rect(),0,str);

  m_painter.beginNativePainting();

  m_vao.bind();
  QMatrix4x4 mat;
  mat.ortho(rect());
  GLfloat top=boundingRect.height()+20;
  GLfloat bottom=top+0.75*m_height;
  GLfloat left=m_width-boundingRect.width()-10;
  GLfloat right=left+0.25*boundingRect.width();
  legend_left = left; // Add it to the private members.
  vertex2D rect_vertices[4]={{{left,top},1},{{right,top},1},
  			     {{right,bottom},-1},{{left,bottom},-1}};
  m_gradient_vbo.bind();
  m_ibo.bind();
  GLshort indices[6]={0,1,2,2,3,0};
  m_ibo.write(0,indices,sizeof(indices));
  m_gradient_vbo.write(0,rect_vertices,sizeof(rect_vertices));

  m_program->addShaderFromSourceCode(QOpenGLShader::Vertex,LEGEND_VERTEX_SHADER);
  m_program->addShaderFromSourceCode(QOpenGLShader::Fragment,FRAGMENT_SHADER);
  m_program->bind();
  m_program->link();
  m_program->setAttributeBuffer("legend_coords",
				GL_FLOAT,
				0,
				2,
				sizeof(vertex2D));
  m_program->setAttributeBuffer("legend_hue",
				GL_FLOAT,
				sizeof(rect_vertices[0].coords),
				1,
				sizeof(vertex2D));
  m_program->setUniformValue("ortho",mat);
  
  m_vao.release();
  m_vao.bind();
  m_program->enableAttributeArray("legend_coords");
  m_program->enableAttributeArray("legend_hue");
  glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_SHORT,0);

  m_vao.release();
  m_program->disableAttributeArray("legend_coords");
  m_program->disableAttributeArray("legend_hue");
  m_program->release();
  m_program->removeAllShaders();
  m_gradient_vbo.release();
  m_ibo.release();

  m_painter.endNativePainting();
  m_painter.drawText(m_width-boundingRect.width()-10,boundingRect.height()+10,str);

  // Adding legend ticks
  QString vals[7]={"pi","2pi/3","pi/3","0","-pi/3","-2pi/3","-pi"};
  float curr_height=top;
  float dist=(bottom-top)/6;
  for (int i=0;i<7;i++){
    m_painter.drawLine(right,curr_height,right+10,curr_height);
    m_painter.drawText(right+15,curr_height+0.4*boundingRect.height(),vals[i]);
    curr_height+=dist;
  }
}

Following is the macro in shaders.h, which defines the vertex shader for the legend:

#define LEGEND_VERTEX_SHADER					\
  "attribute vec2 legend_coords;\n"				\
  "attribute float legend_hue;\n"				\
  "uniform mat4 ortho;\n"					\
  "varying float f_hue;\n"					\
  "void main(void){\n"						\
  "    gl_Position=ortho*vec4(legend_coords,0,1);\n"		\
  "    f_hue=legend_hue;\n"					\
  "}"

Add Axes

The function addAxes uses the painter and the viewport matrix to draw axes and add the ticks. You hove noticed that the matrices are of size 4×4 and the vectors are of length 4; when a matrix is multiplied by 4, you should divide coordinates x,y by coordinate w to find the vector’s location on the 2D window. Special treatment is added to the slanted z-axis, where SVG is used:

void GraphWidget::addAxes(void){

  QVector4D startVec,endVec;

  // Draw the x-axis
  startVec=viewport*transform*QVector4D(-1,-0.2,1.1,1);
  endVec=viewport*transform*QVector4D(1,-0.2,1.1,1);
  draw3DLine(startVec,endVec);
  add_x_ticks();

  // Draw the y-axis
  startVec=viewport*transform*QVector4D(-1,-0.2,1.1,1);
  endVec=viewport*transform*QVector4D(-1,1.4,1.1,1);
  draw3DLine(startVec,endVec);
  add_y_ticks();

  // Draw the z-axis
  startVec=viewport*transform*QVector4D(1,-0.2,-1,1);
  endVec=viewport*transform*QVector4D(1,-0.2,1.1,1);
  draw3DLine(startVec,endVec);
  add_z_ticks();
}

void GraphWidget::draw3DLine(QVector4D fromVec, QVector4D toVec){
  m_painter.drawLine(fromVec.x()/fromVec.w(),
		     m_height-fromVec.y()/fromVec.w(),
		     toVec.x()/toVec.w(),
		     m_height-toVec.y()/toVec.w());
}

void GraphWidget::add_x_ticks(){
  QVector4D start_point,end_point;
  QString vals[]={"-1","-0.5","0","0.5","1"};
  int i=0;
  for (float loc=-1;loc<=1;loc+=0.5){
    start_point=viewport*transform*QVector4D(loc,-0.2,1.1,1);
    start_point.setX(start_point.x()/start_point.w());
    start_point.setY(m_height-start_point.y()/start_point.w());
    end_point=start_point;
    end_point.setY(end_point.y()+5);
    m_painter.drawLine(start_point.x(),start_point.y(), end_point.x(),end_point.y());
    m_painter.drawText(start_point.x(),end_point.y()+fontSize+1,vals[i++]);
  }
  const char *text="Im(Z)";
  QVector4D start_vector=viewport*transform*QVector4D(-1,-0.2,1.1,1);
  QVector4D end_vector=viewport*transform*QVector4D(1,-0.2,1.1,1);
  float text_start=(start_vector.x()+end_vector.x())/(2.*end_vector.w())-2*fontSize;
  float bottom=m_height-end_vector.y()/end_vector.w()+2*fontSize+5;
  m_painter.drawText(text_start,bottom,text);
}

void GraphWidget::add_y_ticks(){
  QVector4D start_point,end_point;
  QString vals[]={"0.0","0.2","0.4","0.6","0.8","1.0","1.2","1.4"};
  int i=0;
  for (float loc=0;loc<=1.41;loc+=0.2){
    start_point=viewport*transform*QVector4D(-1,loc,1.1,1);
    start_point.setX(start_point.x()/start_point.w());
    start_point.setY(m_height-start_point.y()/start_point.w());
    end_point=start_point;
    end_point.setX(end_point.x()-5);
    m_painter.drawLine(start_point.x(),start_point.y(), end_point.x(),end_point.y());
    m_painter.drawText(end_point.x()-2*fontSize,end_point.y()+0.5*fontSize,vals[i++]);

  }
  const char *text="|W(z)|";
  QVector4D start_vector=viewport*transform*QVector4D(-1,-0.2,1.1,1);
  QVector4D end_vector=viewport*transform*QVector4D(1,1,1.1,1);
  float text_y_position=(start_vector.y()+end_vector.y())/(2.*end_vector.w())-2*fontSize;
  float text_rightmost=start_vector.x()/start_vector.w()-6*fontSize;
  m_painter.drawText(text_rightmost,text_y_position,text);
}

void GraphWidget::add_z_ticks(){
  QVector4D start_point,end_point;
  QString vals[]={"-1","-0.5","0","0.5","1"};
  int i=0;
  for (float loc=-1;loc<=1;loc+=0.5){
    start_point=viewport*transform*QVector4D(1,-0.2,loc,1);
    start_point.setX(start_point.x()/start_point.w());
    start_point.setY(m_height-start_point.y()/start_point.w());
    end_point=start_point;
    end_point.setY(end_point.y()-5);
    m_painter.drawLine(start_point.x(),start_point.y(), end_point.x(),end_point.y());
    if (i!=1){
      m_painter.drawText(end_point.x()-fontSize/2.,end_point.y()-1,vals[i]);
    }
    i+=1;
  }
  add_z_text();
}

void GraphWidget::add_z_text(){
  char *svg_str=(char *)calloc(strlen(SVG_TEXT_STR)+1,sizeof(char));
  const char *text="Re(Z)";
  QVector4D start_point,end_point;
  start_point=viewport*transform*QVector4D(1,-0.2,1,1);
  start_point.setX(start_point.x()/start_point.w());
  start_point.setY(m_height-start_point.y()/start_point.w());
  end_point=viewport*transform*QVector4D(1,-0.2,-1,1);
  end_point.setX(end_point.x()/end_point.w());
  end_point.setY(m_height-end_point.y()/end_point.w());
  qreal inRadians=qAtan2(end_point.y()-start_point.y(),end_point.x()-start_point.x());
  qreal inDegrees=qRadiansToDegrees(inRadians)+180;
  float centerX=(start_point.x()+end_point.x())/2.;
  float centerY=(start_point.y()+end_point.y())/2.;
  float lenOfText=(strlen(text)-1.5)*fontSize;
  float textX=centerX-lenOfText;
  float textY=centerY+fontSize;
  
  
  sprintf(svg_str,SVG_TEXT_STR,textX,textY,fontSize,inDegrees,centerX,centerY,text);
  cout<<svg_str<<endl;
  cout<<"Length of svg_str"<<strlen(svg_str)<<endl;
  cout<<"Lenght of SVG_TEXT_STR"<<strlen(SVG_TEXT_STR)<<endl;
  
  QSvgRenderer renderer((QByteArray(svg_str)));
  renderer.render(&m_painter,rect());

}

The function add_z_text renders the SVG text using an object of call QSvgRendered. It accepts a byte array made from a string. It can also take a file name. The macro SVG_TEXT_STR is defined in file “svg.h” as follows:/code

#define SVG_TEXT_STR							\
  "<svg>"								\
  "    <text x='%.0f'" 							\
  "          y='%.0f'"							\
  "          font-size='%dpx'"						\
  "          stroke='white'"						\
  "          fill='white'"						\
  "          transform='rotate(%.0f ,  %.0f ,  %.0f)'>%s</text>"	\
  "</svg>"

Painting the Graph

Following is the function that paints the graph:

void GraphWidget::paintGraph(){
  m_painter.beginNativePainting();
  m_vao.bind();
  m_point_vbo.bind();
  m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, GRAPH_VERTEX_SHADER);
  m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, FRAGMENT_SHADER);
  m_program->bind();
  m_program->link();
  m_program->setAttributeBuffer("graph_coords",
				GL_FLOAT,
				0,
				3,
				sizeof(graph_point));
  m_program->setAttributeBuffer("hue",
				GL_FLOAT,
				3*sizeof(GLfloat),
				1,
				sizeof(graph_point));
  m_program->setUniformValue("transform",transform);
  m_vao.release();
  m_vao.bind();
  m_program->enableAttributeArray("graph_coords");
  m_program->enableAttributeArray("hue");
  glDrawArrays(GL_POINTS,0,NUM_ARGS*NUM_MAGNS);
  m_program->removeAllShaders();
  m_program->release();
  m_point_vbo.release();
  m_vao.release();
  m_painter.endNativePainting();
}

The Main Function

Following is the code of the main function:

int main(int argc, char *argv[]){
  QApplication app(argc, argv);
  GraphWidget w;
  w.show();

  return app.exec();
}

OpenGL Made Simple with Qt5

Drawing and painting a 3D image with GL – the Graphic Library – is not as trivial as doing the same on a 2D canvas: no 3D versions of the 2D canvas drawing methods are defined in GL classes. Instead, programs written with GL send data to the GPU (Graphic Processing Unit), and directions for organizing the data in data structures and for the rendition of data to the screen. The data is stored in buffers, and processed by shader programs.

Once you know how to use the OpenGL API, drawing the following image is simple:

The way Qt simplifies the use of GL are as follows:

  • it offers classes instead of integers to wrap GL resources: buffers, textures, vertex array and more.
  • the classes include overloaded functions.
  • the developer can create shaders without specifying the GL version.
  • Qt manages the main loop.

The following sections will describe the process of painting a 3D surface.

Initializing

To start working with GL, create an object of class QOpenGLWidget. Better create a subclass of QOpenGLWidget, if you work with Qt 5.4 or a later version. Don’t perform any GL operations before the method initializeGL is called. If you extend OpenGLWidget, any GL operation should be performed by one of the following methods: initializeGL, paintGL and resizeGL.

In initializeGL place operation to be performed once only at the initialization time. Some examples of initializeGL can be found in

Qt Assistnat->Qt Widgets->C++ Classes->QOpenGLWidget

click the code examples link.

Following is the initialization stage of my program to paint the triangle:

The Class

Following is a definition of the ExtendedOpenGLWidget class:

#include <QtWidgets/QOpenGLWidget>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLShader>
#include <QOpenGLContext>


class ExtendedOpenGLWidget:public QOpenGLWidget {
    private:
        QOpenGLVertexArrayObject m_vao;
        QOpenGLBuffer m_vbo;
        QOpenGLShaderProgram *m_program;
        QOpenGLShader *m_fragmentShader, *m_vertexShader;
    public:
        ExtendedOpenGLWidget(QWidget *parent=nullptr);
        ~ExtendedOpenGLWidget();
        void initializeGL();
        void paintGL();
};

The class is defined in file “ui_opengl.h”.

Include Headers

#include <ui_opengl.h>
#include <iostream>

using namespace std;

The file “ui_opengl” is the header file generated by th Qt Designer. The rest can be used for debug printing.

The vertices

Vertices coordinates and colors are defined in my program as follows:

struct vertex {
float coords[3];
float colors[4];
};

struct vertex {
     float coords[3];
     float colors[4];
 };
 vertex vertices[]={
     {{-0.9, -0.9, 0.0}, {0.0, 0.0, 1.0, 1.0}},
     {{ 0.9, -0.9, 0.0}, {0.0, 1.0, 0.0, 1.0}},
     {{ 0.0,  0.9, 0.0}, {1.0, 0.0, 0.0, 1.0}}
 };

The above is a definition of a global variable. If you put it in a class, you will have to specify array sizes.

Constructor

The only thing the constructor does is call the parent constructor:

ExtendedOpenGLWidget::ExtendedOpenGLWidget(QWidget *parent):QOpenGLWidget(parent){
 }

The initializeGL method

This function creates the vertex array object(VAO). If the VAO cannot be created, it cannot be bound to the context. Your program can draw with or without it. In my example, I bind the VAO.

What actually does the drawing is the shader program. For it to draw you should link it at run time with the shaders. If you use Qt 5, the class QOpenGLShaderProgram exists with a method addShaderFromSourceCode, so you can create program in separate text files without the need to recompile your GL program when the shader code changes.

The example in Qt Assistant->Qt GUI->C++ Classes->QOpenGLShaderProgram does not complete the example in Qt Assistnat->Qt Widgets->C++ Classes->QOpenGLWidget; Don’t use it if you bind a buffer, because the coordinates and colors defined by setAttributeArray and setUniformValue will not set the correct locations of the colors of coordinates for the drawing method. In addition, only use coordinates specified in pixel if the GL Widget is of fixed size.

Following is an initializeGL method for coordintes whose values are between -1 and 1, and the color RGBA values are floats between 0 and 1:

void ExtendedOpenGLWidget::initializeGL(){
     glEnable(GL_BLEND);
     glEnable(GL_DEPTH_TEST);
     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
     m_vao.create();
     if (m_vao.isCreated()){
         cout<<"Created"<<endl;
         m_vao.bind();
     }
     m_vbo.create();
     m_vbo.bind();

     // Allocating buffer memory and populating it
     m_vbo.allocate(vertices, sizeof(vertices));

     m_fragmentShader=new QOpenGLShader(QOpenGLShader::Fragment);
     m_fragmentShader->compileSourceFile("../fragmentshader.txt");
     m_vertexShader=new QOpenGLShader(QOpenGLShader::Vertex);
     m_vertexShader->compileSourceFile("../vertexshader.txt");
     m_program=new QOpenGLShaderProgram();
     m_program->addShader(m_fragmentShader);
     m_program->addShader(m_vertexShader);
     m_program->link();
     m_program->bind();

     // Arguments of setAttributeBuffer in this example:
     //    1: A string, the corresponding attribute in the shaders.
     //    2: the type of each array element.
     //    3: the start point relative to the beginning of the current buffer.
     //    4: the number of elements in each vector.
     //    5: stride: the difference in bytes between two successive vectors
     m_program->setAttributeBuffer("vertex", GL_FLOAT,0,3,sizeof(vertex));
     m_program->setAttributeBuffer("color", GL_FLOAT,sizeof(vertices[0].coords),4,sizeof(vertex));
     m_program->enableAttributeArray("vertex");
     m_program->enableAttributeArray("color");
     m_vao.release();
 }

Shaders

The program uses two shaders:

  • a vertex shader – outputs the coordinates of which point
  • a fragment shader – outputs the color of which point.
  • Following are the files:

Vertex shader:

 attribute highp vec3 vertex;
 attribute highp vec4 color;
 varying highp vec4 f_color;
 void main(void){
     gl_Position=vec4(vertex,1.0);
     f_color=color;
 }

Fragment shader:

 varying mediump vec4 f_color;
 void main(void){
     gl_FragColor = f_color;
 }

Drawing

The method paintGL draws the image. In this example, it uses the function glDrawArrays with the mode GL_TRIANGLES. In 3D the only 3D polygon type a graphical program always knows how to fill because the vertices are on the same plan.

Following is the paintGL method:

void ExtendedOpenGLWidget::paintGL(){
     cout<<"paint"<<endl;
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     m_vao.bind();
     // Arguments of setAttributeBuffer in this example:
     //    1. mode - how the shaders will draw the array
     //    2. start - first enabled array
     //    3. count - the number of indices.
     glDrawArrays(GL_TRIANGLES,0,3);
     m_vao.release();
}

The main function

An Open GL Widget may be a top-level window or a widget inside another container. In the following main function, the widget is inside a main window:

int main(int argc, char *argv[]){
     QApplication app(argc, argv);
     QMainWindow mainWindow;
     Ui_MainWindow mainObject;
     mainObject.setupUi(&mainWindow);
     mainWindow.show();
     return app.exec();
}

A Destructor

If you want to avoid warnings at the end of the execution, you better define a destructor to clean up the allocated objects.

Following is the code of one:

ExtendedOpenGLWidget::~ExtendedOpenGLWidget(){
     makeCurrent();
     m_vao.destroy();
     m_vbo.destroy();
     delete m_fragmentShader;
     delete m_vertexShader;
     delete m_program;
     doneCurrent();
 }

Learn more about OpenGL here

Design and Develop GUI with Qt5

Qt allows you to develop a multi-platform application, that you can develop a GUI application on one platform and deploy to many others (if you avoid system-specific extra modules). Qt contains modules for creating widgets from elementary ones to OpenGL 3D, which I will discuss later. In addition it contains module with other functionalities an application may need such as SQL, XML, web channels, web views and web sockets.

Qt can be downloaded from https://www.qt.io/ or installed using the UNIX/Linux package installers.

In addition to the QT libraries and header files, there are 3 useful resources:

  • Qt Designer – A GUI tool for the generation of Python and C++ headers from a form graphically designed.
  • Qt Assistant – A reference to the Qt classes, Example, the QMake tutorial, etc.
  • qmake – an easy-to-use utility that creates the Makefile.

In the following sections, I will show how to create a project using the three tools:

Creating the Main Window

Qt Designer is an editor with which GUI designers and developers can communicate. With the designer you can create, save and edit Designer UI Files (*.ui) and create class files from them for Python and C++ programs. The QMake program discussed later can use the “.ui” file to generate a header file from it. The file can be edited by creating top level windows such as widows and dialog, and dragging and dropping widgets into them. You can also access and modify their properties; if you are a developer and have a Desiger UI file, you can find names of object in your program.

Starting:

As you start the QT Designer, the following dialog pops up:

If you don’t see the dialog, choose File->New from the editor’s menu bar, to make the dialog pop up.

Choose your top-level widget from the “templates/forms” menu, and click Create; a window will be created:

Now, from the right-hand Property Editor, you can change some properties, for example: let us change the window title:

Adding widgets and promoting them

Now, you can choose widgets from the left-hand side of the editors and drag-and-drop them into the window:

For example, let us add a push button, and change its QAbstractButton property “text” to “Push me!”.

One of the other properties we can change is the QObject property “objectName”, which will be the name of the appropriate field of class QPushButton in the created class.

By right-clicking inside the widget and choosing “Promote to..” from the context-menu, you can choose to use a class that inherits from the gadget’s class. Don’t forget to define the new class and its constructor and methods.

Previewing

You can view your design by clicking Form->Preview from the editor’s menu bar.

Saving the created class

If you don’t want the Make program to generate a header file, you can save it from the Qt Designer tool.

  1. Choose Form->View C++ Vode/View Python Code from the editor’s menu bar.
    a dialog will open.
  2. Click the “save” icon to save the created header file.

Adding Functionality

This section will describe a little Hello World program – in C++ – that uses a header file created wit QtDesigner. The purpose of the program is to print “Hello, world” to the standard output, when the “Push Me” button is clicked. To add the mouse event handling the class QPushButton has been promoted to Extended Button. This section will describe the program, and will help you train yourself using Qt Assistant.

The main function

To learn what a the main function of a Qt Application with widgets should look like:

  1. Start the Qt Assistant if not started yet.
  2. Click the Contents tab
  3. Click Qt Widgets (highlighted in the following image:

You” see a heading reading “Getting Started Programming with Qt Widgets”. Scroll down and see the contents of a main source line.

In the Hello World program the code is as follows:

 #include "ui_example.h"
 #include <iostream>

 using namespace std;

 ExtendedButton::ExtendedButton(QWidget *parent):QPushButton(parent) {
 }
 void ExtendedButton::mouseReleaseEvent(QMouseEvent *event){
     cout<<"Hello, world!"<<endl;
 }
 int main(int argc, char **argv){
     QApplication a(argc,argv);
     Ui_MainWindow mainObject;
     QMainWindow mainWindow;
     mainObject.setupUi(&mainWindow);
     mainWindow.show();
     return a.exec();
 }

The main file includes the constructor of the class ExtendedButton. Its role is to call the parent’s constructor. Another function implemented is mouseReleaseEvent, which is an event handler. You can learn from Qt Assistant, that the method mouseReleaseEvent is an event handler by clicking Qt Widget->QWidget in the right-hand Contents tab, and then click the link reading “events” under detailed description in the pages contents.

The class ExtendedButton is defined in “extendedbutton.h”. Following is the definition:

#include <QtWidgets/QPushButton>

 class ExtendedButton:public QPushButton {
     public:
         ExtendedButton(QWidget *parent=nullptr);
         void mouseReleaseEvent(QMouseEvent *event);
 };

The main object of the application is defined in “ui_example.h”, the file generated by “Qt Designer”, you better avoid changing it manually if you want to modify the “.ui” file from which it was generated. You’ll see a warning at the beginning. The role of the Class Ui_MainWindow is to contain main window (or central widget) and its underlying widgets as public members. The method setupUi binds them.

Creating the qmake file

“qmake” is an easy-to-use utility that generates a Makefile to be used by the make command to generate binaries, objects, libraries. etc. Qt Assistant includes a Qt Manual. Following is the content of the example’s qmake file named ‘qmake.pro’:

TARGET=executable
SOURCES+=main.cpp
HEADERS+=extendedbutton.h
FORMS+=example.ui
DESTDIR=bin
QT = core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

If you want the Make program to create the main header file, you can include the “.ui” file generated by Qt Designer in a variable named “FORMS”. The “make” program in turn will generate a header file from it with the prefix “ui_” added to the “.ui” file name. For example, from a file named “example.ui” the “make” program will generate a header file named “ui_example.h”. In this case, don’t add “ui_example.h” to the variable “HEADERS”.

If you want to know what to add to QT, go to the <Class Category>->C++ Classes and click the Detailed Description link.If you want to run qmake without command line arguments, call the qmake file ‘qmake.pro’.

Main Window Operation In Matplotlib

Matplotlib is a MATLAB-like library that allows Python programmers to create images and animations. For example, you can easily draw a graphic representation of functions with Y (and maybe Z) values generated by numpy and scipy functions.
Matplotlib can also be interactive and handle events. The command mpl_connect is used for connecting an event with a callback function.

The Backend Layer

Someone on the IRC has challenged me with questions on how to perform some operations when the window is closed. In addition, I want the window title to be other than the default, “Figure 1”.
enter image description here

Well, the layer that handles the main window is the backend layer,
To find what backend Matplotlib uses, you can add the line
print type(fig.canvas)
The result may be something like:
<class 'matplotlib.backends.backend_gtkagg.FigureCanvasGTKAgg'>
This means that the backend used is ‘GtkAgg’.
With the function ‘dir’, I’ve found that the canvass has a function named get_toplevel, and the returned value of fig.canvass.get_toplevel() is an object of type gtk.Window.
This object has the methods of a GTK window. So you can change its title with the ‘set_titlemethod. For example:
fig.canvas.get_toplevel().set_title(‘Rubic Cube’)
You can tell your application what to do when the user closes the window, by calling its 'connect' method, with 'destroy' for first arguments.
For example:
fig.canvas.get_toplevel().connect(‘destroy’, destroyFunc, ‘Goodbye, cruel world!’)
destroyFunc` is a function that accept 2 arguments (3 if a class member): the widget where the event has occurred and additional user defined data.
More about Python FTK can be found at http://www.pygtk.org/pygtk2tutorial/index.html

Last but not least, you can specify the backend Matplotlib will use, by calling the ‘use’ method of matplotlib.
For example:
matplotlib.use('GTKAgg')

Note: This method should be called before importing ‘pyplot’.

Written with StackEdit.

Cubic Equations And The Complex Field

One thing I wish to see in languages such as PHP is to find them supporting the complex type. Complex numbers are more than vectors in 2D, and I wish to see expression containing them parsed just like the ones with real numbers. Python supports them, and you have to import ‘cmath’ to use functions of a complex variable. To import cmath type

import cmath

For example, complex numbers are useful in solving cubic equations even if all its roots are real. And cubic equations can be used for Bézier curve manipulations.

Following is the Cardan formula for solving a cubic equation

Be x^3 + ax^2 + bx + c=0 a cubic equation.

Step 1

Convert the equation to the form latex y^3 + py + q = 0
Use the Taylor series formula, to find a k, such that y=x-k:
Be P(x) = x^3 + ax^2 + bx + c
Then, P(x) = P(k) + P'(k)x + {P''(k)x^2 \over 2} + {P'''(k)x^3 \over 6}
P(k) = k^3 + ak^2 + bk + c
P'(k) = 3k^2 + 2ak + b
P''(k) = 6k + 2a
P'''(k) = 6

Because P”(k)=0, 6k + 2a=0, thus:
k= - {a \over 3} .
p=P'(k) = b - {a^2 \over 3}
q=P(k) = {2a^3 \over 27} - {ba \over 3} + c

For example,
x^3 - 7x^2 +14x - 8 = 0
will become
y^3 -2{1 \over 3}y - {20 \over 27} = 0
In Python:

a = -7
b = 14
c = -8
p = b - a**2 / 3.
q = 2*a**3 / 27. - b*a/3. - 8

Step 2

Find 2 numbers u and v that will help us solve the equation. If y=u+v , then the new equation will be:
u^3 + v^3 + (p + 3uv)(u + v) + q = 0
We can find u,v such that (p + 3uv) = 0,
Thus,

and latex u^3 + v^3 = -q
Since p+3uv=0, u^3{v^3} = {-p^3 \over 27}
From both equations, we get that latex u^3 and latex v^3 are the roots of the quadratic equations
t^2 +qt - {q^3 \over 27} = 0
The roots of the quadratic equations are:
(1) u^3 = - {q \over 2} + \sqrt{{q^2 \over 4} + {p^3 \over 27}}
(2) v^3 = - {q \over 2} - \sqrt{{q^2 \over 4} + {p^3 \over 27}}
In Python, the inner root can be computed using:

innerRoot = cmath.sqrt(q**2 / 4. + p**3/27.)

Now, u and v are cubic roots of (1) and (2) respectively. They must satisfy 3uv=-p.
In Python, you get your initial u using:

u=(-q / 2. + innerRoot) ** (1/3.)

If the pair u,v does not satisfy 3uv = -p, you can multiply your v by
$latex-1 + i \sqrt 3 \over 2 $
until the pair satisfies the condition.
Now, having a solution, get the next by multiplying u by $latex-1 + i \sqrt 3 \over 2 and v by latex-1 – i \sqrt 3 \over 2

In our example:
u^3 = {20 \over 54} + \sqrt{{-263 \over 729}}
v^3 = {20 \over 54} - \sqrt{{-263 \over 729}}

Let’s find our three solutions:
u_1= (0.8333333333333335+0.28867513459481187j), v_1=(0.8333333333333335-0.28867513459481187j)
Thus, latex $y_1 = (1.666666666666667+0j)$
u_2 = (-0.6666666666666659+0.5773502691896264j), v_2=(-0.6666666666666657-0.5773502691896264j)
Thus, y_2 = (-1.3333333333333317+0j)
u_3 = (-0.1666666666666676-0.8660254037844383j), v_3=(-0.1666666666666677+0.866025403784438j)
Thus, y_3 = (-0.3333333333333353-2.220446049250313e-16j)

(The above values are output from Python script. The real results look much better.)
Now, to get the roots of the original equation, add k={-a \over 3} to each y.
In our example,
k = 2{1 \over 3}
Thus,
x_1 = 4, x_2=1, x_3=2

Writing expressions is much easier and more readable when the language supports the complex type.

Meltdown – The Computer Lab Prank

I remember that little prank from the days I was a student. You work on an X terminal, and out of the blue, all the display contents gradually disappear’ Pixel after pixel turns black. But don’t worry – you’ll regain control over your display shortly. shortly.
Everyone can access other X terminal display, and mess with it.

How Does It Work?

This program is a simple one using the GDK library, Gnome’s window management package. Including ‘gdk.h’ will also include:

The Program’s Flow

The main function of the program performs the following steps:
1. Initialize GDK.
2. Create a window whose dimensions are the same as those of the root window.
3. Make the window’s background transparent.
4. Make the window a full-screen window.
5. Add an event handler. to handle Expose events.
The event handler will perform the following steps:
1. Create a list of columns and lengths (number of blackened pixels).
2. Create the Graphics Context for the window.
3. Blacken pixels until all pixels are black.
4. Quit the main loop.

Includes And Structures:

#include <stdio.h>
#include <stdlib.h>  
#include <gdk/gdk.h>

GMainLoop *mainloop;
GList *list;

typedef struct col_and_length_t{
  short col;  // Column number
  short len;  // Number of blackened pixels.
} col_and_length;`

The main function:

int main(int argc, char *argv[]){
  gdk_init(NULL, NULL);
  GdkDisplay *disp=gdk_display_get_default();
  GdkScreen *scr = gdk_display_get_default_screen (disp);
  GdkWindow *root = gdk_screen_get_root_window(scr);
  int rootWidth = gdk_window_get_width(root);
  int rootHeight = gdk_window_get_height(root);
  GdkWindowAttr attr;
  attr.width=rootWidth;
  attr.height=rootHeight;
  attr.x=0;
  attr.y=0;
  attr.window_type = GDK_WINDOW_TOPLEVEL;
  attr.wclass=GDK_INPUT_OUTPUT;

  GdkWindow *newWin=gdk_window_new(root,&attr, GDK_WA_X | GDK_WA_Y);
  gdk_event_handler_set (eventFunc, newWin, NULL);
  GdkRGBA color;
  color.alpha=0;

  gdk_window_set_background_rgba(newWin, &color);
  gdk_window_fullscreen(newWin);
  gdk_event_handler_set (eventFunc, newWin, NULL);
  gdk_window_show(newWin);
  mainloop = g_main_new (TRUE);
  g_main_loop_run (mainloop);
  gdk_display_close(disp);

return 0;
}

The event handler

void start_meltdown(GdkWindow *newWin, int height){
  cairo_t *gc=gdk_cairo_create(newWin);
  cairo_set_line_width(gc,2);
  cairo_set_source_rgb (gc, 0, 0, 0);
  int cell_no,size;
  GList *link;
  col_and_length *link_data;
  size=g_list_length(list);

  while(size>0){
    cell_no=random() % size;
    link = g_list_nth(list,cell_no);
    link_data = (col_and_length *)link->data;
    cairo_move_to(gc, link_data->col, link_data->len);
    cairo_rel_line_to(gc, 0, 1);
    cairo_stroke(gc);
    link_data->len++;
    if (link_data->len >= height){
      list=g_list_remove_link(list, link);
      --size;
    }
  }
  g_main_loop_quit(mainloop);
}

void eventFunc(GdkEvent *evt, gpointer data){
  GdkWindow *newWin = (GdkWindow *)data;
  if (gdk_event_get_event_type(evt) == GDK_EXPOSE && gdk_event_get_window (evt) == newWin){
    int width=gdk_window_get_width(newWin);
    int height=gdk_window_get_height(newWin);
    int i;
    for (i=0; i<width;i++){
      col_and_length *cell=(col_and_length *)calloc(sizeof(col_and_length), 1);
      cell->col=i;
      cell->len=0;
      list = g_list_append(list, cell);
    }
    start_meltdown(newWin,height);
  }

}

Compiling

In linux, compiling a program is easy thanks to the pkg-config command.
Run the following from the command line:

gcc meltdown.c `pkg-config --cflags --libs gdk-3.0` -o meltdown

Now, to run the program type:

./meltdown

Written with StackEdit.

Teach Yourself D3.js

D3 is a JavaScript framework used for data visualization.
Learning how to work with D3.js is very easy. If you begin with the basics, you’ll see how easy it is to use D3, because:

  • the DOM function names are much shorter than those in the legacy DOM library.
  • the same functions are used for both setting and getting values.
  • setters return a value, so you can keep working with the same element without repeating its name.

If you already know some framework, you probably feel that there’s nothing new under the sun, which is good because it makes learning the new stuff easier.

So, What’s New?

In D3, you can:
* add and remove SVG and HTML elements using the setters and getter.
* set and get the element’s properties, attributes, styles, text and… data

Getting Started

First, download the framework from http://d3js.org
After you include it in your script, you can use selections, that is wrappers for elements.
To get a selection, use one of the functions:

  • d3.select(selector) – to select the first element matching selector.
  • d3.selectAll(selector) – to select the all elements matching selector.

Like the d3 object, selections has their select and selectAll methods to select descendant.

If selection is a selection, it’s easy to understand what selection.text(), selection.attribute(), selection.style(), selection.insert(), and selection.append() do. But, what does selection.data() do?

Data And Update Selection

The selection’s method data() helps you use the HTML DOM as a database.
selection.data() returns an array of all data that belongs to the elements of the selectionץ The data is held in the property __data__ of the element.
selection.data(arr, func) – defines an update selection and distribute the elements of array arr to the elements of selection.
func(d,i)is a function that:

  • receives d an element of arr and its index i.
  • returns a record key.

If func is not passed, the key is the element’s index.

A recommended way to create an update selection is:
var updSelection = selection1.selectAll(selector).data(arr)

Now, updSelection is a selection of existing elements whose data has been changed.
In addition, updSelection has to methods:

  • updSelection.enter() – returns place holders for elements to be created under selection1.
  • updSelection.exit()– returns elements that have lost their data, and are usually to be removed.

Using the data

The methods attr, style, property, text and data can accept a function as a value to set. The function will be called for each element of the selection’s data. And its arguments will be the array element and its index.

Now, let us see it in an example.
The following code arranges data in a donut layout:

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Testing Pie Chart</title>
<script type="text/javascript" src="d3.min.js"></script>

<style type="text/css">
.slice text {
font-size: 12pt;
font-family: Arial;
}
</style>
</head>
<body>
<script type="text/javascript">
var width = 300, 
    height = 300, 
    radius = 100, 
    color = d3.scale.category20c(); //builtin range of colors


// Step 1: creating a container for the donut chart, and attach raw data to it.
var vis = d3.select("body")
            .append("svg")     //create the SVG element inside the <body>
            .datum([{"label":"first", "value":20},
                    {"label":"second", "value":10},
                    {"label":"third", "value":30},
                    {"label":"fourth", "value":25}])       
            .attr({"width": width, "height": height})  
            .append("g")
            .attr("transform", "translate(" + radius + "," + radius + ")") //move the center of the pie chart from 0, 0 to r, r



// Step two: defining the accessors of the layour function. 
//           The function 'pie' will use the 'value' accessor to compute the arc's length.
var pie = d3.layout.pie() 
            .value(function(d) { return d.value; })
            .sort(function(a,b){return a.label<b.label?-1:a.label==b.label?0:1;});

// Step 3: Create the update selection. 
var updateSelection = vis.selectAll("g") 
                         .data(pie);    // This will use the function 'pie' to create arc data from the row data attached to the visualisation element.


// Step 4: Using the data in the update selection to create shape elements.
var arc = d3.svg.arc() 
            .outerRadius(radius)
            .innerRadius(radius / 2)
;
updateSelection.enter()
               .append('g')
               .attr('class','slice')
               .append("path")
               .style("fill", function(d, i) { return color(i); } ) //set the color for each slice to be chosen from the color function defined above
               .attr("d", arc);                                     // Create the arc from the arc data attached to each element.

updateSelection.append("text") 
               .attr("transform", 
                     function(d) { 
                       return "translate(" + arc.centroid(d) + ")"; 
                     })
               .attr("text-anchor", "middle")                  
               .text(function(d, i) { return d.data.label; }); 


</script>
</body>
</html> 

And following is the chart:
enter image description here

For more funcions, see the API Reference.

Bézier Curves

The Bézier curve is a popular way to draw curves in graphic editors such as GIMP and Inkscape. A curve of degree n is defined using n+1 points, where the first and last are the start and end points of the curve, respectively, and the rest are control points.
For example:
fff
The curve in the image above is a cubic Bézier curve. It has start and end points (filled with blue) and two control points (with no fill).
Each control point is attached by a straight line to a start or an end point, for a reason:

  • The control points allows the user to control the curve intuitively.
  • The straight line between the start(or end) point and its control point is tangent to the curve at the start(or end) point.

The Definition

A Bézier curve is defined as the collection of points that are the result of the function
B(t) for every t in [0,1].
A linear Bézier is simply a straight line between to points P0 and P1. The function is:
(1 – t)BP0 + tBP1

For n>1, Be P0, P1 … Pn the list of the curve’s points. Then the curve’s function is defined as
BP0P1…Pn(t) = (t – 1)BP0P1…Pn-1(t) + tBP1P2…Pn(t)

Or, in its explicit form:

(Not a very precise definition because 00 is not a number, so use the value 1 instead.)

This equation can be proved easily using the Pascal triangle.
From the explicit definition, you can see that the translation is done by adding the same coordinates to which of the curves start, end and control points.
because:
Rotations and translations are done by a transform matrix. So, if T is a transform matrix:
TBP1,P2,…Pn = BTP1,TP2,…TPn

About Tangents

Now, in a Bézier curve, BP0P1…Pn(t), The line P0 – P1 is tangent to the curve at point P0, and Pn – Pn-1 is tangent to the curve at point Pn

To prove this we’ll have to show that the derivative of a Bézier curve of degree n at the start and end points is a non-zero scalar multiplied by the difference between P1 and P0, and between Pn and Pn-1.
That scalar is n.

For n=1;
BP0,P1 = (1 – t)P0 + tP1
Let’s derive:
B’P0,P1 = -P0 + P1

Good!

Let’s assume it’s correct for n, and prove for n+1
BP0,P1…,Pn+1(t) = (1 – t)BP0,P1…,Pn(t) + tBP1,P2…,Pn+1(t)
Let’s derive:
B’P0,P1…,Pn+1(t) = -BP0,P1…,Pn(t) + (1-t)B’P0,P1…,Pn(t) + BP1,P2…,Pn+1(t) + tB’P1,P2…,Pn+1(t)

Now, to get the tangent to the curve at p0, let;s assign t=0:
B’P0,P1…,Pn+1(0) = -BP0,P1…,Pn(0) + B’P0,P1…,Pn(0) + BP1,P2…,Pn+1(0) =
= – P0 + n(P1 – P0) + P1 = (n+1)(P1 – P0)

Good!
Now, to get the tangent to the curve at p0, let;s assign t=1:
B’P0,P1…,Pn+1(1) = -BP0,P1…,Pn(1) + BP1,P2…,Pn+1(1) + B’P1,P2…,Pn+1(1) =
= – Pn + Pn+1 + n(Pn+1 – Pn) + P1 = (n+1)(Pn+1 – Pn)

QED

SVG supports Bézier curves of up to the third degree. A path consisting of such curves are good approximations of shapes provided that you have enough points.

HTML5 Canvases & Transforms

Browsers supporting HTML5 allow you to draw on the browser’s screen without preparing an image file before. Drawing on a canvas is done using the wonderful Javascript language. If you want to draw a 2-dimensional image on canvas element ‘cnv’, get the drawing context using:

var ctx = cnv.getContext("2d")

And use that context to draw everything using the standard functions:

moveTo, lineTo, arc, fillRect, etc.

Learn more about drawing here.

You can use the functions to create more complicated shapes easily thanks to transform functions:

Transformations: Linear Algebra Made Easy

The origin(0,0) of the canvas is defined to be the top-left pixel of the canvas element. And the coordinates are given in number of pixels. This can change by using transforms.

The transformation functions are:

  • scale(x,y): this will make every shape x times wider and y time taller.
  • translate(x,y): now coordinate (0,0) will be shifted x units to the right, and y units down.
  • rotate(angle): will rotete the axes by given angle in radians.
  • transform(a,b,c,d,e,f): If you want to use a transformation matrix
  • setTransfrom(a,b,c,d,e,f): reset all transform, and perform transform(a,b,c,d,e,f).

a,b,c,d,e,f are values in the matrix:

Transform Matrix
The values a,b,c,d are used for rotating and scaling. e,f for translating.

Other useful methods of the context are:

  • ctx.save() – to save current transform in a stack (Last In First Out).
  • ctx.restore() – to retrieve the trnasform from the top of the stack.

An Example – The Koch Snowflake

The algorithm for drawing the Koch Snowflake can be found in the post Drawing The Koch Snowflake Fractal With GIMP.

Here’s an example in Javascript:

        function drawSide(ctx, len){
          if (len > 1) {
            var thirdOfLen = len / 3.;

            var rotationAngles = [0, -Math.PI / 3, 2 * Math.PI / 3., -Math.PI / 3];

            rotationAngles.forEach(function(val){
              
              if (val != 0){
                ctx.translate(thirdOfLen, 0);
                ctx.rotate(val);
              }
              ctx.save();
              drawSide(ctx, thirdOfLen);
              ctx.restore();
            });

          } else {
            ctx.moveTo(0,0);
            ctx.lineTo(len,0);
            //ctx.stroke();
          } 
        }

        ctx.translate(startX, startY);
        for (i=0; i<3; i++){
          ctx.save();
          drawSide(ctx, sideLength);
          ctx.restore();
          ctx.translate(sideLength,0);
          ctx.rotate(2*Math.PI/3);
        }
        ctx.stroke();
      }

Warning: using ctx.stroke() after every little line you draw might make your code inefficient, and slow down your browser.

Transformation functions are also part of the Processing language.

The Processing Language…

Processing is a programming language used mainly for animations and graphical applications. If your program is written in pure Processing, the same code can be run in many tools that use their own language: Windows with Java applets, HTML pages with Javascript, Android applications, etc. Running an application  is done by choosing a mode from the Processing GUI tool.

Here’s a screen shot of a Processing Window containing a program to draw a regular polygon:

processingGUI
Drawing a Polygon

Now, to run the program , push the “Play” button, or choose Sketch->Run, and you’ll see the result:

regularPolygon
The polygon in a Java applet.

Now, to run this on Android, I commented out the call to the function ‘size’, following is the result on in an emulator window:

polygonInAndroid
Polygon in Android

You can add to your programs command specific to the mode in which you run. For example you can  add an ‘alert’ command in Javascript mode. Better do it in separate files.

You can download Processing here.

To learn more about pure Processing function go to the function reference.