Tous les articles par admin

Normalisation d'un quaternion

Considérons le quaternion suivant :

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

La normalisation est donnée par la définition suivante :

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

Où :

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

La représentation générale est donnée par :

 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]

Gyroscope et quaternions

Un gyroscope mesure une vitesse angulaire autour des trois axes X, Y et Z du repère du capteur que nous nomerons respectivement \omega_X, \omega_Y et \omega_Z. En intégrant cette vitesse angulaire, il est théoriquement possible d'estimer l'orientation au cours du temps. Définissons le vecteur S_{\omega} contenant les vitesses angulaires :

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

\omega_X, \omega_Y et \omega_Z sont exprimés en rad/s.

Considérons maintenant le quaternion dérivé qui décrit le changement d'orientation lié à la mesure du gyroscope :

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

Uù :


  • \frac{dQ_k}{dt} est la dérivée à l'instant k exprimée dans l'espace des quaternions.
  • \hat{Q}_{k} est l'orientation estimée à l'instant k.

En intégrant cette dérivée, il devient possible d'estimer l'orientation au cours du temps:

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

Pour certaines applications, le quaternion doit être normalisé après l'intégration:

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

Exemple

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

Téléchargement


Matrices de rotation

Rotation autour de l'axe des X

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 autour de l'axe des Y

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 autour de l'axe des Z

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]

Quaternions et rotations

Un quaternion est un nombre complexe de dimension 4 qui peut être utilisé pour représenter l'orientation d'un corps rigide dans un espace à trois dimensions. La définition générale d'un quaternions est donnée par :

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

Representation

Considérons un vecteur \vec{V} défini par trois coordonnées (V_x, V_y and V_z) et \theta un angle de rotation autour de \vec{V} :

quaternions

Le quaternion associé à cette transformation est donné par :

 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 autour des axes primaires


En s'appuyant sur la formule précédente, nous pouvons calculer le quaternion qui va définir la rotation autour de chaque axe :

Rotation autour de X

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

Rotation autour de Y

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

Rotation autour de Z

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

Rotation généralisée

Un vecteur défini en trois dimensions (ou un point) peut subir une rotation définie par un quaternion en utilisant la relation basée sur le produit de quaternion et son conjugué :

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

V_A et V_B sont les quaternions associés aux vecteurs \vec{v_A} et \vec{v_B}. Chaque quaternion contient un 0 inséré à la place du premier élément (angle de rotation) afin de créer un quaternion avec 4 éléments, les autres éléments sont donnés par les coordonnées des vecteurs.
\vec{v_A} et \vec{v_B} est le même vecteur explicité respectivement dans les repères A et B.

Exemple

Cet exemple montre la rotation d'une éllipse définie dans le plan {\vec{X},\vec{Y}} :

elipse_rotation

Cet exemple utilise la bibliothèque de quaternions pour Matlab du Dr Madgwick's Matlab:

Quaternion conjugué

Le conjugué d'un quaternion est représenté par le symbole ^*. Considérons le quaternion Q défini par:

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

Le conjugué de Q est défini par:

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

Le conjugué peut être utilisé pour permuter les repères relatifs lors de la définition d'une orientation. Par exemple, si {}^BQ_A décrit l'orientation du repère B par rapport au repère A, le conjugué de {}^BQ_A^*={}^AQ_B est décrit l'orientation du repère A par rapport au repère B.

Produit de quaternions

Le produit de quaternions est représenté par le symbole \otimes. Considérons les deux quaternions Q_1 et 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}

Le produit est défini par:

 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

L'algèbre des quaternions n'est pas commutative :

 Q_1 \otimes Q_2 \neq Q_2 \otimes Q_1

Filtre de Kalman Etendu (EKF)

Cet article détaille les équations du filtre de Kalman

Prediction

Prédiction de l'état :

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

Où:


  • \hat{x}_i^p est a prédiction de l'état à l'instant i.
  • \hat{x}_{i} est l'état estimé à l'instant i.
  • f est une fonction dérivable qui prédit l'évolution de l'état en fonction de l'état précédent (prediction) et de l'entrée du système (u_i)
  • u_i est l'entrée du système à l'instant i.

Prédiction de l'incertitude (ou de la covariance) :

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

Où:


  • P_i^p est la matrice de covariance associée à l'état \hat{x}_i^p à l'instant i.
  • P_i est la matrice de covariance associée à l'état \hat{x}_{i}.
  • Q_i est la covariance liée à la prédiction (ou bruit) du système.
  • F_i est la matrice de transition. Elle est donnée par la Jacobienne de f()=\frac{df}{dx}

Mise à jour

Innovation or erreur résiduelle :

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

Où:


  • \widetilde{y}_i est l'erreur de mesure : c'est la différence entre la mesure (ou observation) z_i et la mesure estimée depuis l'état prédit \hat{x}_i^p.
  • z_i est l'observation (or mesure) depuis l'état réel x_i.
  • h est une fonction dérivable qui permet de passer de l'espace d'état vers l'espace des observations.

Covariance de l'innovation résiduelle :

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

Où:


  • S_i est la matrice de covariance associée à la mesure d'erreur \widetilde{y}_i.
  • R_i est la matrice de covariance associée au bruit de mesure.
  • H_i est une matrice de transition qui qui permet de passer de l'espace d'état vers l'espace des observations. Elle est donné epar la Jacobienne de H_i=\frac{dh}{dx}.

Gain optimal de Kalman :

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



  • K_i est le gain de Kalman. Cette matrice contient la pondération entre les prédictions et les mesures. C'est cette matrice qui va permettre de réaliser la fusion entre la prédiction et les différentes observations.

Mise à jour de l'état estimé :

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

Mise à jour de la covariance estimée

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

Où:


  • I est la matrice identité.

Filtre de Kalman

Cet article détaille les équations du filtre de Kalman

Prediction

Prédiction de l'état :

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

Où:


  • \hat{x}_i^p est a prédiction de l'état à l'instant i.
  • \hat{x}_{i} est l'état estimé à l'instant i.
  • F_i la matrice de transition. Elle prédit l'évolution de l'état en fonction de l'état précédent (prediction).
  • B_i est la matrice qui va permettre de déterminer l'influence de l'entrée du système à l'instant i sur l'état du système. En d'autre mots, elle permet la transition de l'espace d'entré (vecteur u_i) vers l'espace d'état.
  • u_i est l'entrée du système à l'instant i.

Prédiction de l'incertitude (ou de la covariance) :

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

Où:


  • P_i^p est la matrice de covariance associée à l'état \hat{x}_i^p à l'instant i.
  • P_i est la matrice de covariance associée à l'état \hat{x}_{i}.
  • Q_i est la covariance liée à la prédiction (ou bruit) du système

Mise à jour

Innovation or erreur résiduelle :

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

Où:


  • \widetilde{y}_i est l'erreur de mesure : c'est la différence entre la mesure (ou observation) z_i et la mesure estimée depuis l'état prédit \hat{x}_i^p.
  • z_i est l'observation (or mesure) depuis l'état réel x_i.
  • H_i est une matrice de transition qui permet de passer de l'espace d'état vers l'espace des observations.

Covariance de l'innovation résiduelle :

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

Où:


  • S_i est la matrice de covariance associée à la mesure d'erreur \widetilde{y}_i.
  • R_i est la matrice de covariance associée au bruit de mesure.

Gain optimal de Kalman :

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



  • K_i est le gain de Kalman. Cette matrice contient la pondération entre les prédictions et les mesures. C'est cette matrice qui va permettre de réaliser la fusion entre la prédiction et les différentes observations.

Mise à jour de l'état estimé :

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

Mise à jour de la covariance estimée

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

Où:


  • I est la matrice identité.

Exemple de filtre de Kalman

Le filtre de Kalman est un outil mathématique couramment utilisé pour réaliser de la fusion de données provenant de différents capteurs. We allons illustré son utilisation sur une exemple simple permettant de comprendre comment le filtre fonctionne. COnsidérons un robot situé en face d'un mur qui ne peut se déplacer que dans une seule direction. Supposons que le robot est équipé de deux capteurs : un capteur de vitesse et un capteur de distance (range finder). Nous supposerons que les deux capteurs sont bruité.

Robot

Le but est ici d'estimer, le plus précisément possible, la position x du robot:

position

Les entrées du système sont une mesure de distance bruitée et une mesure de vitesse elle aussi buitée:

rangefinder

Velocity

Resultat

result

Code source

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)');

Téléchargement

Cet example a été inspiré par l'excellent tutorial de Bradley Hiebert-Treuer "An Introduction to Robot SLAM (Simultaneous Localization And Mapping)"

Loi normale

La loi normale (or Gaussienne) est un modèle statistique classiquement utilisé pour estimer l'incertitude d'un capteur. La loi est donné par la formule suivante:

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

où:


  • \sigma est l'écart-type (racine carré de la variance)
  • c est le centre de la gaussienne

variance=\sigma^2

Voici quelques exemples de lois normales:
Examples

La somme des probabilités doit être égale à un, par conséquent, l'aire suivante a une superficie de un:

Surface

68, 95 et 99.7% de la surface sont inclus respectivement entre \sigma, 2.\sigma and 3.\sigma:
distribution

OpenGL et Qt-Creator

Cet article est un simple exemple d'utilisation d'OpenGL dans une fenêtre Qt. Cet example a été réalisé avec les versions suivantes:

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

Capture d'écran

Cette application affiche un repère 3 axes et un nuage de points formant un cube.
capture

Installation des logiciels


Il peut être nécessaire d'installer la boite à outils OpenGL GLUT (OpenGL Utility Toolkit):

sudo apt-get install freeglut3 freeglut3-dev

Téléchargement


MPU-9250 et Arduino (IMU 9 axes)

Cet article présente un simple example d'interfaçage du MPU-9250 avec une Arduino. La carte utilisée ici est fabriquée par Drotek : IMU 9DOF - MPU9250 breakout board.

Le MPU-9250 est constitué de plusieurs circuits encapsulé dans un seul boitier, il contient:

  • un accéléromètre 3 axes
  • un gyroscope 3 axes
  • un magnétomètre 3 axes

AxisOrientation

Câblage

Arduino mega

wiring

Codes sources

#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);    
}

Contributions

Code source sur BitBucket ici.

Téléchargements



Scanner I2C pour Arduino

Le programme suivant est un scanner I2C pour Arduino. Il tente de lire une donnée (un octet) à chaque adresse I2C inclue dans l'intervalle [0:255]. Les adresses des modules esclaves qui répondent sont affichées dans le 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 - exemple 24

Cet article fait parti d'un ensemble d’exemples et d'articles sur gtkmm consultables ici. Cette exemple montre comment créer une application avec des onglets. Le premier onglet contient un bouton et le deuxième onglet contient juste un label.

capture

Téléchargement


Code source


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;
}

Héritage en C++

Cet article montre, à travers un exemple simple, comment fonctionne l'héritage. Considérons une classe Parent avec deux fonctions membres (fct1 et fct2). Notons que fct2 est virtuelle. Considérons maintenant une seconde classe Child (qui hérite de la classe Parent) qui contient aussi deux fonctions membres (fct1 et fct2). Toutes ces fonctions sont déclarées en protégées et peuvent être appelées grâce aux fonctions publiques callFunction1 et callFunction2 qui sont déclarées dans la classe Parent. La question est : que va-t-il se passer quand ces fonctions seront appelées de la classe Child ? Dans le cas de fct1, qui n'est pas virtuelle, c'est la fonction fct1 de la classe Parent qui est appelée. En revanche, dans le cas de fct2, qui est virtuelle, c'est la fonction qui est déclarée dans la classe Child qui est appelée. Notons que la fonction de la classe parent peut être appelée avec la syntaxe suivante : Parent::fct2(). Notons enfin que lors de l'instanciation de la classe Child, le constructeur de la classe Parent est appelé en premier. Le programme suivant illustre ces explications :

#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;
}

Ce code affiche les lignes suivantes. Le constructeur parent est apellé avant le constructeur Child. Pour fct1, c'est la fonction de la classe Parent qui est appelée. Pour fct2, c'est la fonction de la classe Child qui est appelée car la fonction est virtuelle.

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

gtkmm - exemple 23

Cet article fait parti d'un ensemble d’exemples et d'articles sur gtkmm consultables ici. Cet exemple montre comment afficher une image (Pixbuf) dans une zone de dessin Cairo. L'image peut être déplacée, redimensionnée, centrée ou ajustée avec la souris. Le redimensionnement est réalisé avec la molette. Les déplacements sont font avec le clic gauche. Le bouton droit permet d'afficher un menu contextuel (popup) avec différentes options illustrées ci-dessous.

capture

Téléchargement


Code source

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();
}