All posts by admin

Quaternion normalization

Let's consider the following quaternion:

 Q=a+b.i+c.j+d.k = \left[ 
\begin{matrix}
a && b && c  && d
\end{matrix}
\right]

The normalization is given by the following formula:

 Q_{normalized}=\frac{Q}{\| Q \| }

Where :

 \| Q \| = \sqrt{ q.\bar{q} } = \sqrt{ \bar{q}.q }=\sqrt {a^2 + b^2 + c^2 + d^2}

General representation:

 Q_{normalized}=
\left[
\begin{matrix}
\frac{a}{\sqrt {a^2 + b^2 + c^2 + d^2}} && 
\frac{b}{\sqrt {a^2 + b^2 + c^2 + d^2}} && 
\frac{c}{\sqrt {a^2 + b^2 + c^2 + d^2}} && 
\frac{d}{\sqrt {a^2 + b^2 + c^2 + d^2}} 
\end{matrix}
\right]

Quaternions and gyroscope

A gyroscope measures the angular rate around the three axis X, Y and Z of the sensor frame named \omega_X, \omega_Y and \omega_Z respectively. By integrating this angular rate, it is theoretically possible to estimate the orientation over time. Let's define the vector S_{\omega}:

 S_{\omega} = \left[ \begin{matrix} 0 && \omega_x && \omega_y && \omega_z \end{matrix} \right]

Where \omega_X, \omega_Y and \omega_Z are expressed in rad/s.

Let's now consider the quaternion derivative that describes the rate of change of orientation:

 \frac{dQ_k}{dt} = \frac{1}{2}.\hat{Q}_{k-1} \otimes S_{\omega}

Where :


  • \frac{dQ_k}{dt} is the derivative at time step k expressed in the quaternion space.
  • \hat{Q}_{k} is the estimated orientation at time step k.

By integrating the quaternion derivative it becomes possible to estimate the orientation over time:

 \hat{Q}_k = \hat{Q}_{k-1}+ \Delta_t.\frac{dQ_k}{dt}

Note that for some applications, the quaternion must be normalized after integration:

 Q_k^n=\frac{\hat{Q}_k}{\| \hat{Q}_k \| }

Example

close all;
clear all;
clc;


%% Initial parameters

% Initial quaternion
Q(1,:)=[1 0 0 0];

% Step time
dt=0.1;

% Initial coordinates of the point
P(1,:)=[1,2,3];



%% Main loop
for i=2:500
    
    %% Simulate gyroscope value 
    % pi/8 rad/s for the 250 first steps
    % pi/16 rad/s for the 250 last steps
    if (i<250) Sw=[0 pi/8 0 0]; else Sw=[0 0 pi/16 0]; end;

    %% Update orientation
    % Compute the quaternion derivative
    Qdot=quaternProd(0.5*Q(i-1,:),Sw);
    % Update the estimated position
    Q(i,:)=Q(i-1,:)+Qdot*0.1;
    % Normalize quaternion
    Q(i,:)=Q(i,:)/norm(Q(i,:));
    
    %% Update point coordinates
    % Compute the associated transformation marix
    M=quatern2rotMat(Q(i,:));
    % Calculate the coordinate of the new point
    P(i,:)=(M*P(1,:)')';
end


%% Display point trajectory
plot3 (P(:,1),P(:,2),P(:,3),'r.');
axis square equal;
grid on;
xlabel ('x');
ylabel ('y');
zlabel ('z');

trajectory

Download


Rotation matrices

Rotation around the X-axis

x_rotation

 R_x(\alpha)=
\left[
\begin{matrix}
1 && 0 && 0 && 0 \\
0 && cos(\alpha) && -sin(\alpha) && 0 \\
0 && sin(\alpha) && cos(\alpha) && 0 \\
0 && 0 && 0 && 1
\end{matrix}
\right]

Rotation around the Y-axis

y_rotation

 R_y(\beta)=
\left[
\begin{matrix}
 cos(\beta) && 0 && sin(\beta) && 0 \\
0 && 1 && 0 && 0 \\
-sin(\beta) && 0 && cos(\beta) && 0 \\
0 && 0 && 0 && 1
\end{matrix}
\right]

Rotation around the Z-axis

z_rotation

 R_z(\gamma)=
\left[
\begin{matrix}
 cos(\gamma) && -sin(\gamma) && 0 && 0 \\
sin(\gamma) && cos(\gamma) && 0 && 0 \\
0 && 0 && 1 && 0 \\
0 && 0 && 0 && 1
\end{matrix}
\right]

Quaternion conjugate

The quaternion conjugate is denoted by ^*. Considere the quaternion Q defined by:

 
Q = \left[ \begin{matrix} a && b && c && d \end{matrix} \right]

The conjugate of Q is defined by:

 
Q^* = \left[ \begin{matrix} a && -b && -c && -d \end{matrix} \right]

The conjugate can be used to swap the relative frames described by an orientation. For example, if {}^BQ_A describes the orientation of frame B relative to frame A, the conjugate of {}^BQ_A^*={}^AQ_B and describes the orientation of frame A relative to frame B.

Quaternion product

The quaternion product is denoted by \otimes. Consider two quaternions Q_1 and Q_2 :

 
\begin{matrix}
Q_1 &=& \left[ \begin{matrix} a_1 && b_1 && c_1 && d_1 \end{matrix} \right] \\
Q_2 &=& \left[ \begin{matrix} a_2 && b_2 && c_2 && d_2 \end{matrix} \right]
\end{matrix}

The product is given by:

 Q_1 \otimes Q_2 = 
\left[
\begin{matrix}
a_1a_2 - b_1b_2 - c_1c_2 - d_1d_2 \\
a_1b_2 + b_1a_2 + c_1d_2 - d_1c_2 \\
a_1c_2 - b_1d_2 + c_1a_2 + d_1b_2 \\
a_1d_2 + b_1c_2 - c_1b_2 + d_1a_2 
\end{matrix}
\right]^\top

The quaternion product is not commutative :

 Q_1 \otimes Q_2 \neq Q_2 \otimes Q_1

Quaternions and rotations

A quaternion is a four-dimensional complex number that can be used to represent the orientation of a ridged body or coordinate frame in three-dimensional space. The general definition of a quaternion is given by:

 Q=a+b.i+c.j+d.k = \left[ 
\begin{matrix}
a && b && c  && d
\end{matrix}
\right]

Representation

Let's consider a vector \vec{V} defined by 3 scalars (V_x, V_y and V_z) and \theta an angle of rotation around \vec{V} :

quaternions

The quaternion associated to this transformation is given by:

 Q = \left[
\begin{matrix}
cos \frac{\theta}{2} && 
-V_x.sin \frac{\theta}{2} &&
-V_y.sin \frac{\theta}{2} &&
-V_z.sin \frac{\theta}{2} 
\end{matrix}
\right]

Rotation around axes


Based on the previous formula, we can now calculate the quaternion defining a rotation around each axis:

Rotation around X

 Q_X=\left[
\begin{matrix}
cos \frac{\theta}{2} && 
-sin \frac{\theta}{2} &&
0 &&
0
\end{matrix}
\right]

Rotation around Y

 Q_Y=\left[
\begin{matrix}
cos \frac{\theta}{2} && 
0 &&
-sin \frac{\theta}{2} &&
0
\end{matrix}
\right]

Rotation around Z

 Q_Z=\left[
\begin{matrix}
cos \frac{\theta}{2} && 
0 &&
0 &&
-sin \frac{\theta}{2} 
\end{matrix}
\right]

General rotation

A three dimensional vector (or point) can be rotated by a quaternion using the following relationship based on the quaternion product and quaternion conjugate:

 V_B = {}^BQ_A \otimes V_A \otimes {}^BQ_A^*

V_A and V_B are the quaternions associated to vectors \vec{v_A} and \vec{v_B}. Each quaternion contains a 0 inserted as the first element (rotation angle) to make them 4 element row quaternions, the other elements are given by the vector coordinates.

\vec{v_A} and \vec{v_B} is the same vector described in frame A and frame B respectively.

Example

This simple example shows the rotation of an ellipse defined in the plane {\vec{X},\vec{Y}}:

elipse_rotation

This example is based on the Madgwick's quaternion library for Matlab:

Extended Kalman filter (EKF)

This post details the Kalman filter equations.

Predict

State prediction:

 \hat{x}_{i}^p = f(\hat{x}_{i-1},u_i)

Where:


  • \hat{x}_i^p is the predicted state at time step i.
  • \hat{x}_{i} is the estimate of state at time step i.
  • f is differential function that describes how the state will change according to the previous state (prediction) and the system input (u_i).
  • u_i is the system input at time step i.

Uncertainty (or covariance) prediction:

 P_i^p = F_i.P_{i-1}.F_i^\top + Q_i

Where:


  • P_i^p is the error covariance matrix predicted at time step i.
  • P_i is the estimated error covariance matrix associated with the estimated state \hat{x}_{i}.
  • Q_i is the system noise covariance matrix.
  • F_i is the transition matrix. It is given by the Jacobian of f()=\frac{df}{dx}

Update

Innovation or measurement residual:

 \widetilde{y}_i = z_i - h(\hat{x}_i^p)

Where:


  • \widetilde{y}_i is a measurement error : this is the difference between the measurement z_i and the estimate measurement from state \hat{x}_i^p.
  • z_i is an observation (or measurement) from the true state x_i.
  • h is a differential function which maps the state space into the observed space.

Innovation (or residual) covariance:

 S_i=H_i.P_i^p.H_i^\top+R_i

Where:


  • S_i is the covariance matrix associated to the measurement error \widetilde{y}_i.
  • R_i is the covariance matrix for the measurement noise.
  • H_i is a transition matrix which maps the state space into the observed space. It is given by H_i=\frac{dh}{dx}

Near-optimal Kalman gain:

 K_i=P_i^p.H_i^\top.S_i^{-1}

Where


  • K_i is the Kalman gain, this matrix contains the balance between prediction and observations. This matrix will weight the merging between predicted state and observations.

Updated state estimate:

 \hat{x}_i=\hat{x}_i^p + K_i.\widetilde{y}_i

Updated estimate covariance

 P_i=(I-K_i.H_i).P_i^p

Where:


  • I is the identity matrix.

Kalman filter

This post details the Kalman filter equations.

Predict

State prediction:

 \hat{x}_{i}^p = F_i.\hat{x}_{i-1} + B_i.u_i

Where:


  • \hat{x}_i^p is the predicted state at time step i.
  • \hat{x}_{i} is the estimate of state at time step i.
  • F_i is the transition matrix. It describes how the state will change according to the previous state (prediction).
  • B_i is a matrix that translates control input at time step i into a predicted change in state. In another words, it maps an input vector u_i into the state space.
  • u_i is the system input at time step i.

Uncertainty (or covariance) prediction:

 P_i^p = F_i.P_{i-1}.F_i^\top + Q_i

Where:


  • P_i^p is the error covariance matrix predicted at time step i.
  • P_i is the estimated error covariance matrix associated with the estimated state \hat{x}_{i}.
  • Q_i is the system noise covariance matrix.

Update

Innovation or measurement residual:

 \widetilde{y}_i = z_i - H_i.\hat{x}_i^p

Where:


  • \widetilde{y}_i is a measurement error : this is the difference between the measurement z_i and the estimate measurement from state \hat{x}_i^p.
  • z_i is an observation (or measurement) from the true state x_i.
  • H_i is a transition matrix which maps the state space into the observed space.

Innovation (or residual) covariance:

 S_i=H_i.P_i^p.H_i^\top+R_i

Where:


  • S_i is the covariance matrix associated to the measurement error \widetilde{y}_i.
  • R_i is the covariance matrix for the measurement noise.

Optimal Kalman gain

 K_i=P_i^p.H_i^\top.S_i^{-1}

Where


  • K_i is the Kalman gain, this matrix contains the balance between prediction and observations. This matrix will weight the merging between predicted state and observations.

Updated state estimate:

 \hat{x}_i=\hat{x}_i^p + K_i.\widetilde{y}_i

Updated estimate covariance

 P_i=(I-K_i.H_i).P_i^p

Where:


  • I is the identity matrix.

Kalman filter example

The Kalman filter is a very useful mathematical tool for merging multi-sensor data. We'll consider a very simple example for understanding how the filter works. Let's consider a robot that move in a single direction in front of a wall. Assume that the robot is equipped with two sensors : a speed measurement sensor and a distance measurement sensor (range finder). We'll consider in the following that both sensors are noisy.

Robot

Our goal is to estimate, as accurately as possible, the position x of the robot:

position

Input of the system are a noisy distance measurement and a noisy velocity measurement:

rangefinder

Velocity

Result

result

Source code

close all;
clear all;
clc;

% Step time
dt=0.1;
% Variance of the range finder
SensorStd=0.08;
% Variance of the speed measurement
SpeedStd=0.5;

%% Compute the robot trajectory and simulate sensors
% Time
t=[0:dt:10];
n=size(t,2)
% Trajectory
x=0.05*t.*cos(2*t);
% Accelerometer
u=[0,diff(x)/dt] + SpeedStd*randn(1,n);
% Range finder
z=x-SensorStd*randn(1,n);



% Display position
figure;
plot (t,x);
grid on;
xlabel ('Time [s]');
ylabel ('Position [m]');
title ('Robot real position versus time x=f(t)');

% Display accelation
figure;
plot (t,u,'m');
grid on;
hold on;
plot (t,[0,diff(x)/dt]+3*SpeedStd,'k-.');
plot (t,[0,diff(x)/dt]-3*SpeedStd,'k-.');
xlabel ('Time [s]');
ylabel ('Velocity [m/s]');
title ('Velocity versus time u=f(t)');

% Display range finder
figure;
plot (t,z,'r');
grid on;
hold on;
plot (t,x+3*SensorStd,'k-.');
plot (t,x-3*SensorStd,'k-.');
xlabel ('Time [s]');
ylabel ('Distance [m]');
title ('Distance versus time z=f(t)');


%% Kalman

B=dt;
F=1;
Q=SpeedStd^2;
H=1;
R=SensorStd^2;

x_hat=[0];
y_hat=[0];
P=[0];
S=[0];

for i=2:n
        
    
    %% Prediction
    
    %State
    x_hat(i)=F*x_hat(i-1) + B*u(i-1);
    
    % State uncertainty
    P(i)=F*P(i-1)*F' + Q;
    
    
    
    %% Update    
    
    % Innovation or measurement residual
    y_hat(i)=z(i)-H*x_hat(i);
    
    % Innovation (or residual) covariance
    S=H*P(i)*H'+R;
    
    K=P(i)*H'*S;
    
    x_hat(i)=x_hat(i)+K*y_hat(i);
    
    P(i)=(1-K*H)*P(i);    
end

figure;
plot (t,x,'k');
hold on;
plot (t,x_hat);
plot (t,x_hat+sqrt(P),'r-.');
plot (t,x_hat-sqrt(P),'r-.');
legend('Real position','Estimated position','Uncertainty');

grid on;
xlabel ('Time [s]');
ylabel ('Distance [m]');
title ('Estimated position (speed only)');

Download

This example has been inspired by the very good tutorial of Bradley Hiebert-Treuer "An Introduction to Robot SLAM (Simultaneous Localization And Mapping)"

Normal distribution

The normal (or Gaussian) distribution is a stochastic model commonly used for estimating sensor uncertainty. The law is given by:

 y(x)=\frac{1}{\sigma.\sqrt{2.\pi}} e^ {-\frac{(x-c)^2}{2\sigma^2}  }

Where:


  • \sigma is the standard deviation (square root of the variance)
  • c is the center of the gaussian

variance=\sigma^2

Here are some examples of normal distributions:
Examples

As the sum of probabilities must be equal to one thus the following surface is equal to one:

Surface

68, 95 and 99.7% of the surface is included in respectively \sigma, 2.\sigma and 3.\sigma:
distribution

OpenGL and Qt-Creator

This post is a simple example of an OpenGL widget display in a Qt window. This example has been programmed with the following software versions:

  • Xubuntu 14.04 LTS
  • Qt Creator 3.0.1
  • Qt 5.2.1 (GCC 4.8.2, 64 bit)

Screenshot

This application display a 3-axis frame and a cube made of points:
capture

Software installation


You may need to install the OpenGL Utility Toolkit (GLUT):

sudo apt-get install freeglut3 freeglut3-dev

Download


MPU-9250 and Arduino (9-Axis IMU)

This post presents a simple example of how to interface the MPU-9250 with an Arduino board. The breakout board used here is the IMU 9DOF - MPU9250 breakout board manufactured by Drotek.

The MPU-9250 is a multi-chip module (MCM) consisting of:

  • 3-Axis accelerometer
  • 3-Axis gyroscope
  • 3-Axis magnetometer

AxisOrientation

Wiring

Arduino mega

wiring

Source code

#include 

#define    MPU9250_ADDRESS            0x68
#define    MAG_ADDRESS                0x0C

#define    GYRO_FULL_SCALE_250_DPS    0x00  
#define    GYRO_FULL_SCALE_500_DPS    0x08
#define    GYRO_FULL_SCALE_1000_DPS   0x10
#define    GYRO_FULL_SCALE_2000_DPS   0x18

#define    ACC_FULL_SCALE_2_G        0x00  
#define    ACC_FULL_SCALE_4_G        0x08
#define    ACC_FULL_SCALE_8_G        0x10
#define    ACC_FULL_SCALE_16_G       0x18



// This function read Nbytes bytes from I2C device at address Address. 
// Put read bytes starting at register Register in the Data array. 
void I2Cread(uint8_t Address, uint8_t Register, uint8_t Nbytes, uint8_t* Data)
{
  // Set register address
  Wire.beginTransmission(Address);
  Wire.write(Register);
  Wire.endTransmission();
  
  // Read Nbytes
  Wire.requestFrom(Address, Nbytes); 
  uint8_t index=0;
  while (Wire.available())
    Data[index++]=Wire.read();
}


// Write a byte (Data) in device (Address) at register (Register)
void I2CwriteByte(uint8_t Address, uint8_t Register, uint8_t Data)
{
  // Set register address
  Wire.beginTransmission(Address);
  Wire.write(Register);
  Wire.write(Data);
  Wire.endTransmission();
}


// Initializations
void setup()
{
  // Arduino initializations
  Wire.begin();
  Serial.begin(115200);
  
  // Configure gyroscope range
  I2CwriteByte(MPU9250_ADDRESS,27,GYRO_FULL_SCALE_2000_DPS);
  // Configure accelerometers range
  I2CwriteByte(MPU9250_ADDRESS,28,ACC_FULL_SCALE_16_G);
  // Set by pass mode for the magnetometers
  I2CwriteByte(MPU9250_ADDRESS,0x37,0x02);
  
  // Request first magnetometer single measurement
  I2CwriteByte(MAG_ADDRESS,0x0A,0x01);


}


long int cpt=0;
// Main loop, read and display data
void loop()
{
  
  // _______________
  // ::: Counter :::
  
  // Display data counter
  Serial.print (cpt++,DEC);
  Serial.print ("\t");
  
 
 
  // ____________________________________
  // :::  accelerometer and gyroscope ::: 

  // Read accelerometer and gyroscope
  uint8_t Buf[14];
  I2Cread(MPU9250_ADDRESS,0x3B,14,Buf);
  
  
  // Create 16 bits values from 8 bits data
  
  // Accelerometer
  int16_t ax=-(Buf[0]<<8 | Buf[1]);
  int16_t ay=-(Buf[2]<<8 | Buf[3]);
  int16_t az=Buf[4]<<8 | Buf[5];

  // Gyroscope
  int16_t gx=-(Buf[8]<<8 | Buf[9]);
  int16_t gy=-(Buf[10]<<8 | Buf[11]);
  int16_t gz=Buf[12]<<8 | Buf[13];
  
    // Display values
  
  // Accelerometer
  Serial.print (ax,DEC); 
  Serial.print ("\t");
  Serial.print (ay,DEC);
  Serial.print ("\t");
  Serial.print (az,DEC);  
  Serial.print ("\t");
  
  // Gyroscope
  Serial.print (gx,DEC); 
  Serial.print ("\t");
  Serial.print (gy,DEC);
  Serial.print ("\t");
  Serial.print (gz,DEC);  
  Serial.print ("\t");

  
  // _____________________
  // :::  Magnetometer ::: 

  
  // Read register Status 1 and wait for the DRDY: Data Ready
  
  uint8_t ST1;
  do
  {
    I2Cread(MAG_ADDRESS,0x02,1,&ST1);
  }
  while (!(ST1&0x01));

  // Read magnetometer data  
  uint8_t Mag[7];  
  I2Cread(MAG_ADDRESS,0x03,7,Mag);
  

  // Create 16 bits values from 8 bits data
  
  // Magnetometer
  int16_t mx=-(Mag[3]<<8 | Mag[2]);
  int16_t my=-(Mag[1]<<8 | Mag[0]);
  int16_t mz=-(Mag[5]<<8 | Mag[4]);
  
  
  // Magnetometer
  Serial.print (mx+200,DEC); 
  Serial.print ("\t");
  Serial.print (my-70,DEC);
  Serial.print ("\t");
  Serial.print (mz-700,DEC);  
  Serial.print ("\t");
  
  
  
  // End of line
  Serial.println("");
//  delay(100);    
}

Contributors

Source code on BitBucket here.

Download



I2C scanner for Arduino

The following code is an I2C scanner for Arduino. It requests data from each address within the range [0:255]. I2C slaves that answer are display on the terminal.

#include 


void setup() { 
 //Initialize serial and wait for port to open:
  Serial.begin(115200); 
  
  // prints title with ending line break 
  Serial.println("I2C scanner ::: www.lucidarme.me"); 
} 


void loop() 
{
  // Test each I2C address 
  for (int i=0;i<256;i++)
  {
    // Request data (read one byte)
    Wire.requestFrom(i, 1);

    // If slave answers, a device is found
    if (Wire.available())
    {
      // Display address
      Serial.print(i,DEC);
      Serial.print("\t0x");
      Serial.print(i,HEX);
      Serial.print("\t");    
      Serial.print("0b");
      Serial.print(i,BIN);
      Serial.print("\t");
      Serial.println("Device found");
    }
    
//    delay(100);
  }
  Serial.println("End of scan"); 
  while (1);
} 

gtkmm - example 24

This is part of a more general work dedicated to gtkmm accessible here. This example shows how to create an application with Tabs. The first tab display a button and the second tab display a label.

capture

Download


Source code


main.cpp

#include 
#include 


int main(int argc, char* argv[])
{
    // Initialize gtkmm and create the main window
    Glib::RefPtr app = Gtk::Application::create(argc, argv, "www.lucidarme.me");
    // Create the window
    mainwindow window;
    // Start main loop
    return app->run(window);
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include 
#include 




// The class mainwindow inherits from Gtk::Window
class mainwindow : public Gtk::Window
{
    // Constructor and destructor
public:    
	mainwindow();
	virtual             ~mainwindow();

protected:
    //Signal handlers (run when the button is clicked)
    void                    on_button_clicked();

    //Page selector
    Gtk::Notebook           Selector;

    // Button for the first page
    Gtk::Button             Button;

    // Label for the second page
    Gtk::Label              Label;
};


#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"



// Constructor of the main Window (build ui interface).
mainwindow::mainwindow()
{
    // Resize the main window and set border width
    this->resize(400,200);
    set_border_width(5);

    // Add the label and connect the button
    Button.add_label("Page 1 (button)");
    Button.signal_clicked().connect(sigc::mem_fun(*this,&mainwindow::on_button_clicked));

    // Set label text
    Label.set_text("Page 2 (label)");

    // Add the widgets (button and label) in the notebook
    Selector.append_page(Button, "First");
    Selector.append_page(Label, "Second");

    // This packs the button into the Window (a container).
    add(Selector);
    show_all_children();
}




// Destructor of the class
mainwindow::~mainwindow()
{}

// Call when the button os clicked and display my url
void mainwindow::on_button_clicked()
{
    std::cout << "www.lucidarme.me" << std::endl;
}

C++ inheritance

This post shows through a simple example how inheritance works. Let's consider a first class Parent with two member functions (fct1 and fct2). Note that fct2 is virtual. Let's now consider a second class Child (which inherit from the class Parent) that also contain two member functions (fct1 and fct2). All this functions are protected, and they can be called from outside thanks to the public member functions callFunction1 and callFunction2 that belong to the class Parent. The question is : what will happen when these functions are called from the class Child ? In the case of fct1, which is not virtual, this is the function fct1 from the class Parent that is called. On the other hand, in the case of fct2, which is virtual, this is the function belonging to the class Child that is called. Note that the parent function can be called with the following syntax: Parent::fct2(). Note also that when calling the constructor of Child, the Parent constructor is called first.
The following program illustrates this explaination:

#include 


class Parent
{
public:
    Parent() {std::cout << "Constructor Parent" << std::endl;}

    void            callFunction1() { fct1(); }
    void            callFunction2() { fct2(); }

protected:
    void            fct1() 
    {   std::cout << "Class Parent :: function 1" << std::endl; }
    
    virtual void    fct2() 
    {   std::cout << "Class Parent :: function 2" << std::endl; }
};



class Child : public Parent
{
public:
    Child() {std::cout << "Constructor Child" << std::endl;}

protected:
    void            fct1() 
    {   std::cout << "Class Child :: function 1" << std::endl; }
    
    void            fct2() 
    {   std::cout << "Class Child :: function 2" << std::endl; 
        Parent::fct2(); }
};

int main()
{

    Child c;
    c.callFunction1();
    c.callFunction2();
    return 0;
}

This code output the following lines. The parent constructor is called before the child constructor. For fct1, this is the parent function called. For fct2, this is the child function called because the parent function is virtual.

Constructor Parent
Constructor Child
Class Parent :: function 1
Class Child :: function 2
Class Parent :: function 2

gtkmm - example 23

This is part of a more general work dedicated to gtkmm accessible here. This example shows how to display an image (Pixbuf) into a cairo drawing area. The image can be moved, scaled, fitted or centered with the mouse. Scale is done with the scroll button of the mouse. Move is controlled with the left button. The right button display a popup menu with several options as illustrated below.

capture

Download


Source code

main.cpp

#include 
#include 


int main(int argc, char* argv[])
{
	// Initialize gtkmm and create the main window
	Glib::RefPtr app = Gtk::Application::create(argc, argv, "www.lucidarme.me");
	Gtk::Window window;
	
	// Create the drawing and add an image from file
	ScrollImage Img;    	
	Img.set_image_from_file("gtk.png");
	
	// Insert the drawing in the window
	window.add(Img);
	// Resize the window
	window.resize(600,400);
	// Set the window title
	window.set_title("ScrollImage");
	// Show the drawing
	Img.show();
	
	// Start main loop
	return app->run(window);
}

scrollimage.h

#ifndef SCROLLIMAGE_H
#define SCROLLIMAGE_H

#include 
#include 


// Minimun scale (low boundary)
#define			MIN_SCALE			0.05



class ScrollImage : public Gtk::DrawingArea
{
public:
	
	
	/*!
	 * \brief ScrollImage				Constructor, initialize the widget.
	 */
	ScrollImage();
	
	
	/*!
	 * \brief set_image_from_file		Load the image to display.
	 * \param filename					File name of the image.
	 */
	void set_image_from_file(const std::string& filename);
	
	
	
	
	
protected:
	
	/*!
	 * \brief on_draw					Override the draw handler : update display.
	 *									Don't call this function to update display, call queue_draw().
	 * \param cr						Cairo context.
	 * \return							Always false.
	 */
	virtual bool on_draw(const Cairo::RefPtr& cr);
	
	
	/*!
	 * \brief displayTarget				Display a little target at coordinates x,y.
	 * \param cr						Cairo context.
	 * \param x							x-coordinate of the center of the target.
	 * \param y							y-coordinate of the center of the target.
	 */
	void displayTarget(const Cairo::RefPtr& cr,double x, double y);
	
	
	/*!
	 * \brief on_scroll_event			This function is called when the mouse scroll changes.
	 *									This is a default handler for the signal signal_scroll_event().
	 * \param event						Properties of the event.
	 * \return							Always true (the event is processed).
	 */
	bool on_scroll_event(GdkEventScroll *event);
	
	
	/*!
	 * \brief on_button_press_event		This function is called when a mouse button is pressed.
	 *									This is a default handler for the signal signal_button_press_event().
	 * \param event						Properties of the event.
	 * \return							True if the event is processed, false otherwise.
	 */
	bool on_button_press_event(GdkEventButton *event);
	
	
	/*!
	 * \brief on_button_release_event	This function is called when a mouse button is released
	 *									This is a default handler for the signal signal_button_release_event().
	 * \param event						Properties of the event.
	 * \return							True if the event is processed, false otherwise.
	 */
	bool on_button_release_event(GdkEventButton *event);
	
	
	/*!
	 * \brief on_motion_notify_event	This function is called when the pointer is moved
	 *									This is a default handler for the signal signal_motion_notify_event().
	 * \param event						Properties of the event.
	 * \return							True if the event is processed, false otherwise.
	 */
	bool on_motion_notify_event(GdkEventMotion*event);
	
	
	
protected:
	/*!
	 * \brief resetView					Reset the view (best image fit in the window).
	 * \param winWidth					Width of the window (in pixels).
	 * \param winHeight					Height of the window (in pixels).
	 * \param imgWidth					Width of the image (in pixels).
	 * \param imgHeight					Height of the image (in pixels).
	 */
	void                        fitImage(int winWidth,int winHeight, int imgWidth, int imgHeight);
	
	
	/*!
	 * \brief fit						Set the fit flag to one and update display
	 *									Next call of on_draw will fit the image according to the drawing area.
	 */
	void                        fit();
	
	
	/*!
	 * \brief reset						Reset display (center the image and set scale to 1:1) and update display
	 */
	void                        reset();
	
	
	/*!
	 * \brief resetScale				Set scale to 1:1 and update display
	 */
	void                        resetScale();
	
	
	/*!
	 * \brief setCenter					Center the image on the pointer position from last click
	 */
	void                        setCenter();
	
	/*!
	 * \brief showHideTarget			Show or hide the center target and update display
	 */
	void                        showHideTarget();
	
	
private:
	
	// Image to display
	Glib::RefPtr   image;
	
	// Scale of the image
	double                      scale;
	
	// Coordinates of the image point to place at the center of the window (focussed pixel)
	double                      imgFocusX,imgFocusY;
	
	// Used to memorize last mouse coordinates
	int                         lastXMouse, lastYMouse;
	
	// Flags
	bool                        resetFlag;
	bool                        moveFlag;
	bool                        targetFlag;
	
	// Popup menu and submenus
	
	// Main popup menu
	Gtk::Menu                   m_Menu_Popup;
	Gtk::MenuItem               MenuItemFit;
	Gtk::MenuItem               MenuItemRstView;
	Gtk::MenuItem               MenuItemRstScale;
	Gtk::MenuItem               MenuItemSetCenter;
	Gtk::MenuItem               MenuItemTarget;
	Gtk::SeparatorMenuItem      MenuItemLine1;
	Gtk::SeparatorMenuItem      MenuItemLine2;
	
};

#endif // SCROLLIMAGE_H

scrollimage.cpp

#include "scrollimage.h"


// Constructor
ScrollImage::ScrollImage()
{
	// Set masks for mouse events
	add_events(Gdk::BUTTON_PRESS_MASK |
			   Gdk::BUTTON_RELEASE_MASK |
			   Gdk::SCROLL_MASK |
			   Gdk::SMOOTH_SCROLL_MASK |
			   Gdk::POINTER_MOTION_MASK);

	// ::: Create popup menu :::

	// Add and connect action set pointer as center
	MenuItemSetCenter.set_label("Set center here");
	MenuItemSetCenter.signal_activate().connect(sigc::mem_fun(*this,&ScrollImage::setCenter));
	m_Menu_Popup.append(MenuItemSetCenter);

	// Add a separator
	m_Menu_Popup.append(MenuItemLine1);

	// Add and connect action Fit image to drawing area
	MenuItemFit.set_label("Fit image");
	MenuItemFit.signal_activate().connect(sigc::mem_fun(*this,&ScrollImage::fit));
	m_Menu_Popup.append(MenuItemFit);

	// Add and connect action reset (center and set scale to 1)
	MenuItemRstView.set_label("Reset view");
	MenuItemRstView.signal_activate().connect(sigc::mem_fun(*this,&ScrollImage::reset));
	m_Menu_Popup.append(MenuItemRstView);

	// Add and connect action reset scale to 1
	MenuItemRstScale.set_label("Reset scale");
	MenuItemRstScale.signal_activate().connect(sigc::mem_fun(*this,&ScrollImage::resetScale));
	m_Menu_Popup.append(MenuItemRstScale);

	// Add a separator
	m_Menu_Popup.append(MenuItemLine2);

	// Add and connect action reset scale to 1
	MenuItemTarget.set_label("Hide center target");
	MenuItemTarget.signal_activate().connect(sigc::mem_fun(*this,&ScrollImage::showHideTarget));
	m_Menu_Popup.append(MenuItemTarget);

	// Show the menu
	m_Menu_Popup.show_all();
	// Connect the menu to this Widget
	m_Menu_Popup.accelerate(*this);


	// ::: Initialize view and flags :::
	fit();
	moveFlag=false;
	targetFlag=true;
}



// Load an image from file
void ScrollImage::set_image_from_file(const std::string& filename)
{
	image = Gdk::Pixbuf::create_from_file(filename);
}



// Mouse wheel event detected : update scale
bool ScrollImage::on_scroll_event(GdkEventScroll *event)
{
	// Compute the new scale according to mouse scroll
	double newScale=scale*(1-event->delta_y/20);
	if (newScalex - get_allocated_width()/2.;
	double DeltaY=event->y - get_allocated_height()/2.;
	imgFocusX=imgFocusX + DeltaX/scale - DeltaX/newScale ;
	imgFocusY=imgFocusY + DeltaY/scale - DeltaY/newScale ;;

	// Update scale and redraw the widget
	scale=newScale;
	queue_draw();

	// Event has been handled
	return true;
}


// Mouse button pressed : process mouse button event
bool ScrollImage::on_button_press_event(GdkEventButton *event)
{

	// Check if the event is a left button click.
	if (event->button == 1)
	{
		// Memorize pointer position
		lastXMouse=event->x;
		lastYMouse=event->y;
		// Start moving the view
		moveFlag=true;
		// Event has been handled
		return true;
	}

	// Check if the event is a right button click.
	if(event->button == 3)
	{
		// Memorize mouse coordinates
		lastXMouse=event->x;
		lastYMouse=event->y;
		// Display the popup menu
		m_Menu_Popup.popup(event->button, event->time);
		// The event has been handled.
		return true;
	}
	// Event has not been handled
	return false;
}



// Mouse button released : process mouse button event
bool ScrollImage::on_button_release_event(GdkEventButton *event)
{
	// Check if it is the left button
	if (event->button==1 && moveFlag)
	{
		// End of motion
		moveFlag=false;
		// Update image focus
		imgFocusX -= (event->x-lastXMouse)/scale;
		imgFocusY -= (event->y-lastYMouse)/scale;
		// Update display
		queue_draw();
		return true;
	}
	// Event has been handled
	return false;
}



// Mouse pointer moved : process event
bool ScrollImage::on_motion_notify_event (GdkEventMotion*event)
{

	// If the left button is pressed, move the view
	if (moveFlag)
	{
		// Get mouse coordinates
		int XMouse=event->x;
		int YMouse=event->y;

		// Update image focus
		imgFocusX -= (XMouse-lastXMouse)/scale;
		imgFocusY -= (YMouse-lastYMouse)/scale;

		// Memorize new position of the pointer
		lastXMouse=XMouse;
		lastYMouse=YMouse;

		// Update view
		queue_draw();
		return true;
	}
	// Event has been handled
	return false;
}


// Reset view (scale is set to 1:1 and image is centered)
void ScrollImage::reset()
{
	// Set scale to 1:1
	scale=1;
	// Update image focus
	imgFocusX = image->get_width()/2.;
	imgFocusY = image->get_height()/2.;
	// Update display
	queue_draw();
}


// Reset scale to 1:1
void ScrollImage::resetScale()
{
	scale=1;
	// Update display
	queue_draw();
}


// Center the dispay at the pointer coordinates
void ScrollImage::setCenter()
{
	// Update image focus
	imgFocusX += (lastXMouse - get_allocated_width()/2.)/scale;
	imgFocusY += (lastYMouse - get_allocated_height()/2.)/scale;
	// Update display
	queue_draw();
}

// Best fit of the image in the display
void ScrollImage::fit()
{
	// Set reset flag to true, next call of on_draw will reset display
	resetFlag=true;
	queue_draw();
}


// Hide or show the target center
void ScrollImage::showHideTarget()
{
	// Invert flag
	targetFlag=!targetFlag;
	// Update popup menu according to current status
	if (targetFlag)
		MenuItemTarget.set_label("Hide center target");
	else
		MenuItemTarget.set_label("Show center target");
	// Update display
	queue_draw();
}


// Reset view to fit in the drawing area
void ScrollImage::fitImage(int winWidth,int winHeight, int imgWidth, int imgHeight)
{
	// Compute ratio of the window and the image
	double winRatio=(double)winWidth/winHeight;
	double imgRatio=(double)imgWidth/imgHeight;

	// Check what is the best fit for the image according to the ratio
	if (imgRatio& cr)
{

	// Get the window size
	int winWidth=get_allocated_width();
	int winHeight=get_allocated_height();

	// Get the image size
	int imgWidth=image->get_width();
	int imgHeight=image->get_height();

	// If requested, reset view
	if (resetFlag) fitImage(winWidth,winHeight,imgWidth,imgHeight);

	// Create a new image for display filled with grey
	Glib::RefPtr display = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB,false,8,winWidth,winHeight);
	display->fill(0x5F5F5F00);

	// Compute offset of the source image
	double OffsetX=winWidth/2-imgFocusX*scale;
	double OffsetY=winHeight/2-imgFocusY*scale;
	// Compute top left coordinate of the image in the area
	double Min_X=std::max(0.,OffsetX);
	double Min_Y=std::max(0.,OffsetY);
	// Compute bottom right coordinates of the image in the area
	double Max_X=std::min((double)winWidth,winWidth/2+(imgWidth-imgFocusX)*scale);
	double Max_Y=std::min((double)winHeight,winHeight/2+(imgHeight-imgFocusY)*scale);
	// Compute width and height
	double Width=Max_X-Min_X;
	double Height=Max_Y-Min_Y;

	// Scale image
	image->scale(display,
				 Min_X,Min_Y,
				 Width,Height,
				 OffsetX,OffsetY,
				 scale,scale,Gdk::INTERP_TILES);

	// Display the image in the drawing area
	Gdk::Cairo::set_source_pixbuf(cr,display,0,0);
	cr->rectangle(0,0,winWidth,winHeight);
	cr->fill();

	// Display the center target if requested
	if (targetFlag) displayTarget(cr,winWidth/2.,winHeight/2.);

	// Event has been handled
	return false;
}


// Display a target at coordinates x,y
void ScrollImage::displayTarget(const Cairo::RefPtr& cr,double x, double y)
{
	// Set color to black
	cr->set_source_rgba(0,0,0,0.5);

	// Display black quaters
	cr->arc(x,y,15,0,M_PI/2.);
	cr->line_to(x,y);
	cr->fill();
	cr->arc(x,y,15,M_PI,3.*M_PI/2.);
	cr->line_to(x,y);
	cr->fill();

	// Set color to white
	cr->set_source_rgba(1,1,1,0.5);

	// Display white quaters
	cr->arc(x,y,15,M_PI/2.,M_PI);
	cr->line_to(x,y);
	cr->fill();
	cr->arc(x,y,15,3.*M_PI/2.,0);
	cr->line_to(x,y);
	cr->fill();

	// Set color to black
	cr->set_source_rgba(0,0,0,0.8);

	// Display the cross
	cr->move_to(x-20,y);
	cr->line_to(x+20,y);
	cr->stroke();
	cr->move_to(x,y-20);
	cr->line_to(x,y+20);
	cr->stroke();

	// Display the circle
	cr->arc(x,y,15,0,2*M_PI);
	cr->stroke();
}

gtkmm - example 22

This is part of a more general work dedicated to gtkmm accessible here. This example explains and shows how to resize an image with gtkmm. Let's have a look at the Gdk::Pixbuf::scale function:

void Gdk::Pixbuf::scale	(
const Glib::RefPtr< Gdk::Pixbuf >& dest,
int 		dest_x,
int 		dest_y,
int 		dest_width,
int 		dest_height,
double 		offset_x,
double 		offset_y,
double 		scale_x,
double 		scale_y,
InterpType 	interp_type 
) const

scale



  • dest is the destination image buffer (Gdk::Pixbuf). The function scales and copies the image in a new one (dest). Memory is not automatically allocated for the destination buffer. It means that the image dest must be created previously to the call of Gdk::Pixbuf::scale. Width and height of dest must be in accordance with the other arguments of the function.
  • dest_x and dest_y are the coordinates of the top left point in the destination image. All the pixels on the left side of dest_x and on the upper side of dest_y will remain unchanged.
  • dest_width and dest_height are the size (width and height) of the area to update in the destination buffer.
  • offset_x and offset_y are the offset of the source image in the destination image's frame.
  • scale_x and scale_y are the scale factor applied to the source image before the copy. A scale factor of 1 will keep the original image size. A scale factor of 2 will create an image twice the original and so one.
  • interp_type is the interpolation type used for the transformation. Four types can be used :

    • Gdk::INTERP_NEAREST (top left): nearest neighbor sampling; this is the fastest and lowest quality mode. Quality is normally unacceptable when scaling down, but may be OK when scaling up.
    • Gdk::INTERP_TILES (bottom right): this is an accurate simulation of the PostScript image operator without any interpolation enabled. Each pixel is rendered as a tiny parallelogram of solid color, the edges of which are implemented with antialiasing. It resembles nearest neighbor for enlargement, and bilinear for reduction.
    • Gdk::INTERP_BILINEAR (bottom left): Best quality/speed balance; use this mode by default. Bilinear interpolation. For enlargement, it is equivalent to point-sampling the ideal bilinear-interpolated image. For reduction, it is equivalent to laying down small tiles and integrating over the coverage area.
    • Gdk::INTERP_HYPER (top right): This is the slowest and highest quality reconstruction function. It is derived from the hyperbolic filters in Wolberg's "Digital Image Warping", and is formally defined as the hyperbolic-filter sampling the ideal hyperbolic-filter interpolated image (the filter is designed to be idempotent for 1:1 pixel mapping).

output2

Download

Source code


main.cpp

#include 
#include 


// Scale used for the demonstration
#define         SCALE           8.



int main(int argc, char* argv[])
{
    // Initialize gtkmm library
    Glib::RefPtr app = Gtk::Application::create(argc, argv, "www.lucidarme.me");


    // Load the source image
    Glib::RefPtr image = Gdk::Pixbuf::create_from_file("gtk_icon.png");
    int width=image->get_width();
    int height=image->get_height();
    //image2=image; // = image->scale_simple(image->get_width()*scale,image->get_height()*scale,Gdk::INTERP_NEAREST);


    // __________________________
    // ::: Create output1.png :::

    // Create, fill and save the destination image
    Glib::RefPtr image2 = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB,true,8,width*SCALE,height*SCALE);
    image2->fill(0x0000FFA0);
    image2->save("output1.png","png");
    std::cout << "output1.png created" << std::endl;


    // __________________________
    // ::: Create output2.png :::

    // Scale and copy the first image in the destination image with several type of scalin method
    // Top left
    image->scale(image2,
                 10,10,
                 (width*SCALE)/2.-10,(height*SCALE)/2-10,
                 0,0,
                 SCALE,SCALE,
                 Gdk::INTERP_NEAREST);
    // Top right
    image->scale(image2,
                 (width*SCALE)/2.+10,10,
                 (width*SCALE)/2.-20,(height*SCALE)/2.-10,
                 0,0,
                 SCALE,SCALE,
                 Gdk::INTERP_HYPER);
    // Bottom left
    image->scale(image2,
                 10,(height*SCALE)/2.+10,
                 (width*SCALE)/2.-10,(height*SCALE)/2.-20,
                 0,0,
                 SCALE,SCALE,
                 Gdk::INTERP_BILINEAR);
    // Bottom right
    image->scale(image2,
                 (width*SCALE)/2.+10,(height*SCALE)/2.+10,
                 (width*SCALE)/2.-20,(height*SCALE)/2.-20,
                 0,0,
                 SCALE,SCALE,
                 Gdk::INTERP_TILES);
    // Save the output image
    image2->save("output2.png","png");
    std::cout << "output2.png created" << std::endl;


    // __________________________
    // ::: Create output3.png :::

    // Scale and translate the source image
    image2->fill(0x70707070);
    image->scale(image2,
                 20,20,
                 width*SCALE-40,height*SCALE-40,
                 -width*SCALE/2.,-height*SCALE/2.,
                 SCALE*2,SCALE*2,
                 Gdk::INTERP_BILINEAR);
    image2->save("output3.png","png");
    std::cout << "output3.png created" << std::endl;

    // __________________________
    // ::: Create output4.png :::

    // Scale and translate the source image
    image2->fill(0x70707070);
    image->scale(image2,
				 image2->get_width()/2.,image2->get_height()/2.,
				 image2->get_width()/2.,image2->get_height()/2.,
				 -width*SCALE,-height*SCALE,
                 SCALE*2,SCALE*2,
                 Gdk::INTERP_TILES);
    image2->save("output4.png","png");
    std::cout << "output4.png created" << std::endl;
    return 0;
}

outputs


output1
output2
output3
output4