I. Introduction▲
La manipulation d'images revêt plusieurs aspects. Tout d'abord l'utilisation d'images sur des constituants de l'interface, appelés widgets, à des fins de personnalisation de cette interface, ensuite les traitements simples des images, classement, visionnage, gestion des métadonnées et quelques manipulations de base telles que pivotement, redimensionnement, etc. Pour terminer, le traitement d'image impliquant une modification de ses propriétés, accès aux pixels, colorimétrie, etc.
Nous verrons dans ce tutoriel les deux premiers aspects de la manipulation d'images.
II. Les types d'images Qt▲
QPixmap
Une pixmap est optimisée pour l'affichage à l'écran, celle-ci est chargée en mémoire du
côté serveur X ou dans la mémoire de la carte graphique. Les données stockées dans
l'instance d'un QPixmap ne sont qu'une référence à l'image chargée sur le serveur X.
Son avantage est de profiter des ressources matérielles (accélération graphique) mais,
par contre, cela implique que l'image soit traitée dans la boucle principale du programme
et non pas dans un thread séparé.
Une visionneuse utilisera de préférence des QPixmap.
QImage
QImage permet un accès direct aux pixels de l'image, est indépendant du matériel,
peut être utilisé dans un thread séparé, mais ne profite pas de l'accélération matérielle.
Les QImage seront préférés dans une application de traitement d'images.
QPicture
Les QPicture sont des supports de dessin permettant d'enregistrer une suite de commandes
de QPainter et de les reproduire sur diverses images.
Utilisé pour la sérialisation de dessins, estampillages, etc.
QBitmap
QBitmap désigne une image monochrome, avec un bit à 0 pour l'arrière-plan (ou pixel
transparent) et un bit à 1 pour l'avant-plan (ou pixel opaque).
L'exemple le plus courant étant la création de curseur personnalisé.
QIcon
Les QIcon sont des objets dynamiques en ce sens qu'ils sont redimensionnables selon les
besoins de l'interface et peuvent revêtir divers états : désactivé, actif, survolé, cliqué, etc.
Les QIcon sont généralement construits à partir de Qpixmap.
Nous en verrons des exemples d'utilisation sur des widgets.
III. Les formats d'image▲
Par défaut, Qt peut lire les formats BMP, GIF, ICO, JPEG, JPG, MNG, PBM, PGM, PNG, PPM,
SVG, TIF, TIFF, XBM, XPM
et peut enregistrer sous les formats BMP, ICO, JPEG, JPG, PNG,
PPM, TIF, TIFF, XBM, XPM.
L'ajout d'autres formats doit se faire par plug-in.
Les deux commandes suivantes permettent de savoir quels sont les formats d'image supportés par
votre version de Qt¦ :
Formats supportés en lecture:
for
format in
QtGui.QImageReader.supportedImageFormats
(
):
print
format
Formats supportés en écriture :
for
format in
QtGui.QImageWriter.supportedImageFormats
(
):
print
format
IV. Les modes d'ouverture▲
Diverses manières permettent d'instancier un objet image, entrons dans le code.
# Avec le nom de fichier
image =
QtGui.QImage
(
"fichierImage.jpg"
)
pixmap =
QtGui.QPixmap
(
"fichierImage.jpg"
)
# L'extension peut être séparée
image =
QtGui.QImage
(
"fichierImage"
, "jpg"
)
pixmap =
QtGui.QPixmap
(
"fichierImage"
, "jpg"
)
# ou ignorée
image =
QtGui.QImage
(
"fichierImage"
)
pixmap =
QtGui.QPixmap
(
"fichierImage"
)
# dans ce cas Qt déterminera le format par l'en-tête du fichier.
# ! Lorsque l'extension est donnée, celle-ci doit être exacte.
# Avec une autre instance d'image
image =
QtGui.QImage
(
"fichierImage.jpg"
)
pixmap =
QtGui.QPixmap
(
image)
# QPixmap possède une méthode .toImage()
image =
pixmap.toImage
(
) ==
image =
QtGui.QImage
(
pixmap)
# et une méthode .fromImage(image, flag)
pixmap =
QtGui.QPixmap.fromImage
(
image, 0
)
les différentes valeurs de 'flag' (0 par défaut) peuvent être trouvées ici :
http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qt.html#ImageConversionFlag-enum
À ce stade, il devient utile de savoir si l'image a été correctement chargée.
En effet, en cas de non-ouverture du fichier, Qt ne retourne pas d'erreur mais
crée une "null pixmap", il est donc souvent indispensable de vérifier le bon chargement
de l'image au moyen des méthodes QPixmap.isNull() et QImage.isNull().
if
image.isNull
(
):
print
"Image non valide !"
V. Les supports▲
Les images peuvent être appliquées à différents widgets selon qu'elles
sont destinées à la décoration de l'interface ou qu'elles sont l'objet même de l'application.
Nous allons voir l'utilisation de ces différents supports par la pratique,
pour cela nous allons créer une interface qui nous servira tout au long de ce tutoriel.
imageViewer.py
# -*- coding: utf-8 -*-
import
sys
import
os
from
PyQt4 import
QtCore, QtGui
class
ImageViewer
(
object):
def
setupUi
(
self, Viewer):
Viewer.resize
(
640
, 480
)
Viewer.setWindowTitle
(
u"Exemples d'usage d'images"
)
self.image_1 =
"image1.jpg"
self.image_2 =
"image2.png"
self.centralwidget =
QtGui.QWidget
(
Viewer)
self.gridLayout =
QtGui.QGridLayout
(
self.centralwidget)
self.verticalLayout_2 =
QtGui.QVBoxLayout
(
)
self.horizontalLayout =
QtGui.QHBoxLayout
(
)
# QLabel
self.label =
QtGui.QLabel
(
self.centralwidget)
sizePolicy =
QtGui.QSizePolicy
(
QtGui.QSizePolicy.Preferred,
QtGui.QSizePolicy.Fixed)
self.label.setSizePolicy
(
sizePolicy)
self.label.setPixmap
(
QtGui.QPixmap
(
self.image_1))
self.label.setScaledContents
(
True
)
self.horizontalLayout.addWidget
(
self.label)
self.verticalLayout =
QtGui.QVBoxLayout
(
)
self.label_cmb =
QtGui.QComboBox
(
self.centralwidget)
self.verticalLayout.addWidget
(
self.label_cmb)
spacerItem =
QtGui.QSpacerItem
(
20
, 18
, QtGui.QSizePolicy.Minimum,
QtGui.QSizePolicy.Fixed)
self.verticalLayout.addItem
(
spacerItem)
self.horizontalLayout.addLayout
(
self.verticalLayout)
self.verticalLayout_2.addLayout
(
self.horizontalLayout)
self.horizontalLayout_2 =
QtGui.QHBoxLayout
(
)
# QPushButton
self.pushButton =
QtGui.QPushButton
(
self.centralwidget)
self.pushButton.setText
(
"PushButton"
)
icon1 =
QtGui.QIcon
(
)
icon1.addPixmap
(
QtGui.QPixmap
(
self.image_2),QtGui.QIcon.Normal,
QtGui.QIcon.Off)
self.pushButton.setIcon
(
icon1)
self.horizontalLayout_2.addWidget
(
self.pushButton)
self.push_cmb =
QtGui.QComboBox
(
self.centralwidget)
self.horizontalLayout_2.addWidget
(
self.push_cmb)
# QToolButton
self.toolButton =
QtGui.QToolButton
(
self.centralwidget)
self.toolButton.setText
(
"toolButton"
)
self.toolButton.setIcon
(
icon1)
self.toolButton.setToolButtonStyle
(
QtCore.Qt.ToolButtonTextBesideIcon)
self.horizontalLayout_2.addWidget
(
self.toolButton)
self.tool_cmb =
QtGui.QComboBox
(
self.centralwidget)
self.horizontalLayout_2.addWidget
(
self.tool_cmb)
self.verticalLayout_2.addLayout
(
self.horizontalLayout_2)
self.horizontalLayout_3 =
QtGui.QHBoxLayout
(
)
# QRadioButton
self.radioButton =
QtGui.QRadioButton
(
self.centralwidget)
self.radioButton.setText
(
"RadioButton"
)
self.radioButton.setIcon
(
icon1)
self.horizontalLayout_3.addWidget
(
self.radioButton)
# QCheckBox
self.checkBox =
QtGui.QCheckBox
(
self.centralwidget)
self.checkBox.setText
(
"CheckBox"
)
self.checkBox.setIcon
(
icon1)
self.checkBox.setLayoutDirection
(
QtCore.Qt.RightToLeft)
self.horizontalLayout_3.addWidget
(
self.checkBox)
spacerItem1 =
QtGui.QSpacerItem
(
40
, 20
, QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Minimum)
self.horizontalLayout_3.addItem
(
spacerItem1)
# Colors comboBox
self.colors_cmb =
QtGui.QComboBox
(
self.centralwidget)
self.horizontalLayout_3.addWidget
(
self.colors_cmb)
self.verticalLayout_2.addLayout
(
self.horizontalLayout_3)
# QGraphicsView
self.vue =
QtGui.QGraphicsView
(
self.centralwidget)
self.verticalLayout_2.addWidget
(
self.vue)
self.horizontalLayout_4 =
QtGui.QHBoxLayout
(
)
self.horizontalLayout_4.setObjectName
(
"horizontalLayout_4"
)
self.image_btn =
QtGui.QToolButton
(
self.centralwidget)
self.image_btn.setText
(
"Image"
)
self.image_btn.setObjectName
(
"image_btn"
)
self.horizontalLayout_4.addWidget
(
self.image_btn)
spacerItem2 =
QtGui.QSpacerItem
(
40
, 20
, QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Minimum)
self.horizontalLayout_4.addItem
(
spacerItem2)
self.verticalLayout_2.addLayout
(
self.horizontalLayout_4)
self.gridLayout.addLayout
(
self.verticalLayout_2, 0
, 0
, 1
, 1
)
Viewer.setCentralWidget
(
self.centralwidget)
self.populate_combos
(
)
# Connections
self.label_cmb.currentIndexChanged.connect
(
self.update_label)
self.push_cmb.currentIndexChanged.connect
(
self.update_push_button)
self.tool_cmb.currentIndexChanged.connect
(
self.update_tool_button)
def
populate_combos
(
self):
items =
["setScaledContents(True)"
, "setScaledContents(False)"
]
self.label_cmb.addItems
(
items)
items =
["Modifier..."
, "setAutoDefault()"
, "setDefaut()"
, "setFlat()"
]
self.push_cmb.addItems
(
items)
items =
["Modifier..."
, "setAutoRaise()"
, "ToolButtonIconOnly"
,
"ToolButtonTextOnly"
, "ToolButtonTextBesideIcon"
,
"ToolButtonTextUnderIcon"
, "ToolButtonFollowStyle"
]
self.tool_cmb.addItems
(
items)
names =
["Rouge"
, "Vert"
, "Bleu"
]
colors =
[QtGui.QColor
(
255
, 0
, 0
, 255
), QtGui.QColor
(
0
, 255
, 0
, 255
),
QtGui.QColor
(
0
, 0
, 255
, 255
)]
pix =
QtGui.QPixmap
(
QtCore.QSize
(
30
, 10
))
self.colors_cmb.setIconSize
(
QtCore.QSize
(
30
, 10
))
for
idx, name in
enumerate(
names):
pix.fill
(
colors[idx])
icon =
QtGui.QIcon
(
pix)
self.colors_cmb.addItem
(
icon, name)
def
update_label
(
self, idx):
self.label.setScaledContents
(
not
self.label.hasScaledContents
(
))
def
update_push_button
(
self, idx):
if
not
idx:
return
if
idx ==
1
:
self.pushButton.setAutoDefault
(
not
self.pushButton.autoDefault
(
))
elif
idx ==
2
:
self.pushButton.setDefault
(
not
self.pushButton.isDefault
(
))
else
:
self.pushButton.setFlat
(
not
self.pushButton.isFlat
(
))
def
update_tool_button
(
self, idx):
if
not
idx:
return
if
idx ==
1
:
self.toolButton.setAutoRaise
(
not
self.toolButton.autoRaise
(
))
else
:
self.toolButton.setToolButtonStyle
(
idx-
2
)
if
__name__
==
"__main__"
:
import
sys
app =
QtGui.QApplication
(
sys.argv)
Viewer =
QtGui.QMainWindow
(
)
ui =
ImageViewer
(
)
ui.setupUi
(
Viewer)
Viewer.show
(
)
sys.exit
(
app.exec_
(
))
Dans ce code, vous aurez à remplacer dans les lignes 12 et 13 les chemins des images situées sur votre disque. Pour self.image_2, choisissez de préférence une image de taille réduite. Une icône fera très bien l'affaire.
Remarque sur les chemins des images :
- soit les images sont dans le même dossier que le script, utilisez "imageX.jpg" ;
- soit elles sont dans un sous-dossier (ex. "medias/") utilisez, "medias/imageX.jpg" ;
- soit elles sont dans le dossier parent (vers le haut) utilisez, "../imageX.jpg" ;
- soit vous ne vous en sortez pas, utilisez le chemin complet ;
- d'autre part, "Couché-de-soleil.jpg" retournera une image nulle,
utilisez u"Couché-de-soleil.jpg".
(Non, Qt n'est pas sensible aux fautes d'orthographe.)
Voyons le code, tout d'abord le QLabel, rien de compliqué, un QPixmap ou un QPicture peuvent lui être appliqué directement. Un argument optionnel permet d'ajuster la taille de l'image à l'espace du QLabel, mais cet agrandissement n'est pas proportionnel, l'image peut donc apparaître trop étirée dans un des deux axes.
On ne peut joindre texte et image dans un Qlabel.
Les QPushButton et QToolButton demandent la création d'un QIcon qui sera appliqué avec la méthode setIcon(icon).
Une option permet de déterminer la taille de l'icône mais sera inopérante si la taille choisie est supérieure à celle du bouton. Il est souvent préférable de laisser Qt choisir la taille de l'icône.
Vous remarquerez, dans les combos que ces deux types de boutons n'ont pas les mêmes options d'apparence.
Pour obtenir un résultat comme celui-ci :
on choisira des QPushButton.
Les QRadioButton et QCheckBox demandent aussi un QIcon pour l'insertion d'une image. Ici, les options d'apparence se limitent à la direction du widget, c'est-à-dire que la case à cocher peut être positionnée à droite comme dans le cas de la QCheckBox.
Pour terminer avec les widgets pouvant être décorés au moyen d'images, nous avons un QComboBox où les images sont insérées au moyen de QIcon. Ces icônes étant créées à partir de pixmaps dans la dernière partie de la fonction populate_combos().
Toutes les possibilités d'insertion d'image dans tous les widgets possibles ne peuvent être vues ici, mais les méthodes utilisées dans le code devraient permettre de répondre à toutes les situations.
Des personnalisations d'interface plus poussées, couleur de fond, couleur de texte, image de fond, feront appel au styleSheet.
VI. Visionnage d'images▲
Lorsque l'image est l'objet même de l'application, comme dans une visionneuse, divers widgets
peuvent servir de support tels que QFrame, QWidget ou encore QScrollArea.
Ces widgets impliquent toutefois que notre code implémente les fonctions de positionnement ou
de centrage qui peuvent s'avérer de vrais casse-tête, surtout si l'on désire que la fenêtre soit
redimensionnable ou, dans le cas du QScrollArea où l'apparition d'une barre de défilement repositionne
systématiquement l'image dans le coin supérieur gauche du widget.
Qt nous propose un conteneur beaucoup plus performant pour l'affichage d'image: le QGraphicsView.
Plus exactement, la paire QGraphicsView et QGraphicsScene.
Le QGraphicsView ou la vue, est l'espace physique dans lequel se positionnera la scène. Ce widget hérite de QAbstractScrollArea ce qui nous permettra de profiter de fonctionnalités "ready-to-use" comme nous le verrons avec l'outil panoramique.
Le QGraphicsScene ou la scène, est l'espace virtuel dans lequel nous placerons notre image. Cet espace étant virtuel, il ne doit pas obligatoirement être dimensionné, dans ce tuto, nous lui donnerons cependant, les dimensions de l'image à afficher, pour profiter, entre autres, du centrage automatique de l'image dans la scène.
La vue sera donc une fenêtre sur un espace illimité par défaut.
Dans le code, nous avons utilisé la méthode la plus simple pour instancier la vue :
self.vue =
QtGui.QGraphicsView
(
self.centralwidget)
self.verticalLayout_2.addWidget
(
self.vue)
Nous créerons la scène lorsque nous importerons l'image puisque nous avons choisi de lui donner les dimensions de l'image.
La scène aurait pu être créée dès le départ en utilisant la méthode suivante :
self.scene =
QtGui.QGraphicsScene
(
)
self.vue =
QtGui.QGraphicsView
(
self.scene)
self.verticalLayout_2.addWidget
(
self.vue)
Remarquez : pas de parent pour la scène, la vue a la scène pour parent et c'est toujours la vue que nous plaçons dans le layout.
Complétons notre code, dans le groupe des connexions (lignes 104-106), ajoutons une ligne :
self.image_btn.clicked.connect
(
self.get_image)
ensuite ajoutons la fonction get_image() à la fin de notre classe :
def
get_image
(
self):
img =
unicode(
QtGui.QFileDialog.getOpenFileName
(
Viewer,
u"Ouverture de fichiers"
,
""
, "Image Files (*.png *.jpg *.bmp)"
))
if
not
img:
return
self.open_image
(
img)
Cette fonction permettra de choisir une image en cliquant sur le bouton "Image". Remarquez l'utilisation d'Unicode pour éviter le problème d'ouverture cité plus avant.
Terminons en ajoutant les fonctions open_image() et view_current() :
def
open_image
(
self, path):
w_vue, h_vue =
self.vue.width
(
), self.vue.height
(
)
self.current_image =
QtGui.QImage
(
path)
self.pixmap =
QtGui.QPixmap.fromImage
(
self.current_image.scaled
(
w_vue, h_vue,
QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation))
self.view_current
(
)
def
view_current
(
self):
w_pix, h_pix =
self.pixmap.width
(
), self.pixmap.height
(
)
self.scene =
QtGui.QGraphicsScene
(
)
self.scene.setSceneRect
(
0
, 0
, w_pix, h_pix)
self.scene.addPixmap
(
self.pixmap)
self.vue.setScene
(
self.scene)
C'est ici que les choses deviennent intéressantes, voyons ces fonctions en détail.
La fonction open_image() :
Nous partons du principe que nous afficherons notre image à la taille de la vue, nous implémenterons un zoom ensuite, donc commençons par extraire la taille de la vue w_vue et h_vue, respectivement largeur et hauteur.
Créons notre image en tant que QImage, celle-ci ne sera jamais affichée mais nous en aurons besoin plus tard.
Nous créons ensuite notre pixmap à la dimension de la vue.
Les arguments de la méthode .scaled() :
-
Les modes de redimensionnement :
Qt.IgnoreAspectRatio le rapport largeur/hauteur de l'image originale ne sera pas respecté, dans la majorité des cas cela entraînera une déformation disgracieuse ;
Qt.KeepAspectRatio le rapport de taille sera respecté et le plus petit agrandissement possible sera utilisé. C'est le mode que nous choisissons ;
Qt.KeepAspectRatioByExpanding le rapport de taille sera respecté et le plus grand agrandissement possible sera utilisé.
Note : il n'est pas impossible qu'une barre de défilement apparaisse, les dimensions de la vue retournées par Qt peuvent être dépendantes du système. -
Les méthodes de redimensionnement :
Qt.FastTransformation redimensionnement rapide sans antialias ;
Qt.SmoothTransformation redimensionnement avec antialias, le filtre utilisé est de type bilineaire.
La fonction view_current() :
Nous relevons les dimensions de notre pixmap, w_pix et h_pix.
Nous instancions une scène et nous lui attribuons les dimensions de notre pixmap.
Les deux dernières lignes placent notre image dans la scène et ensuite celle-ci dans la vue.
Variante : Si la scène a été créée directement avec la vue, comme indiqué plus haut, la fonction view_current() se présentera comme ceci :
def
view_current
(
self):
w_pix, h_pix =
self.pixmap.width
(
), self.pixmap.height
(
)
self.scene.clear
(
)
self.scene.setSceneRect
(
0
, 0
, w_pix, h_pix)
self.scene.addPixmap
(
self.pixmap)
la ligne 'self.vue.setScene(self.scene)' ne se justifiant plus ici.
Afin de comprendre pourquoi nous imposons à la scène les dimensions de l'image, je vous propose
de tester le code avec des dimensions très différentes de l'image.
ex. si l'image mesure 2500x1800 px, essayez ceci :
self.scene.setSceneRect
(
0
, 0
, 6000
, 4000
)
# et ceci :
self.scene.setSceneRect
(
0
, 0
, 500
, 300
)
Testez le code et vous constaterez que l'image n'est plus centrée.
Remettez le code dans l'état initial.
self.scene.setSceneRect
(
0
, 0
, w_pix, h_pix)
Comme nous ne sommes jamais satisfaits, implémentons un zoom. Tout d'abord, il faut mettre la vue à l'écoute de la roulette de la souris. Dans la définition de la vue (ligne 84), rajoutons l'évènement wheelEvent.
Nous devons avoir ceci :
# QGraphicsView
self.vue =
QtGui.QGraphicsView
(
self.centralwidget)
self.vue.wheelEvent =
self.wheel_event
self.verticalLayout_2.addWidget
(
self.vue)
et, à la fin de notre code, ajoutons ces fonctions :
def
wheel_event (
self, event):
steps =
event.delta
(
) /
120.0
self.zoom
(
steps)
event.accept
(
)
def
zoom
(
self, step):
w_pix, h_pix =
self.pixmap.width
(
), self.pixmap.height
(
)
w, h =
w_pix *
(
1
+
0.1
*
step), h_pix *
(
1
+
0.1
*
step)
self.pixmap =
QtGui.QPixmap.fromImage
(
self.current_image.scaled
(
w, h,
QtCore.Qt.KeepAspectRatio,
QtCore.Qt.FastTransformation))
self.view_current
(
)
Voyons cela en détail.
La fonction wheel_event() :
event.delta() nous retourne la rotation de la roulette en 1/8 de degré, la plupart des souris
étant crantées tous les 15° nous divisons par 120 pour obtenir le nombre de crans,
plus évident pour l'utilisateur. Autrement dit, "chaque cran = un niveau de zoom" ;
event.delta() sera positif en cas de rotation vers l'avant de la souris et négatif pour
une rotation vers l'utilisateur.
La fonction zoom() :
Récupérons tout d'abord les dimensions de notre pixmap w_pix et h_pix.
Appliquons à ces dimensions notre facteur d'agrandissement comme ceci :
supposons une largeur de pixmap de 600 pxl, un pas de zoom de 0.1 (valeur que vous décidez vous-même)
et deux steps (crans de souris),
nouvelle largeur = 600 * (1 + 0.1 * 2)
Maintenant, c'est ici que nous découvrons l'intérêt d'avoir conservé une instance de notre image originale, en effet, nous ne pouvons nous permettre de recharger l'image depuis le disque pour chaque saut de zoom et d'autre part, si nous redimensionnons directement notre pixmap nous allons voir celle-ci se dégrader de façon exponentielle, chaque redimensionnement amplifiant les erreurs du précédent.
Pour vous en convaincre, modifiez le code comme ceci : dans la fonction zoom() remplacez
self.pixmap =
QtGui.QPixmap.fromImage
(
self.current_image.scaled
(
w, h,
QtCore.Qt.KeepAspectRatio,
QtCore.Qt.FastTransformation))
par ceci :
self.pixmap =
self.pixmap.scaled
(
w, h, QtCore.Qt.KeepAspectRatio,
QtCore.Qt.FastTransformation)
Testez le code, la dégradation de l'image apparaît rapidement.
Il nous manque encore une chose, c'est de pouvoir déplacer l'image "à la main" lorsque celle-ci est plus grande que la vue.
Retournons dans notre code et ajoutons une ligne à la définition de notre vue pour obtenir ceci :
# QGraphicsView
self.vue =
QtGui.QGraphicsView
(
self.centralwidget)
self.vue.setDragMode
(
QtGui.QGraphicsView.ScrollHandDrag)
self.vue.wheelEvent =
self.wheel_event
self.verticalLayout_2.addWidget
(
self.vue)
C'est tout ce dont nous avons besoin pour notre outil panoramique. Les options de la méthode setDragMode() sont :
- QGraphicsView.noDrag supprimer toute fonctionnalité préalablement définie ;
- QGraphicsView.ScrollHandDrag déplacer l'image avec la souris ;
- QGraphicsView.RubberBandDrag tirer un rectangle de sélection.
Relancez le script, zoomez et déplacez l'image, nous avons construit une modeste visionneuse.
VII. Conclusions▲
Nous avons vu ici, les outils élémentaires de Qt permettant une manipulation simple d'images avec peu de lignes de code ainsi que les bases de la personnalisation d'interface.
Toutefois, ceci n'est qu'un aperçu des possibilités de Qt, des techniques plus poussées deviennent
accessibles par une étude des classes suivantes :
QtGui.QImage ;
QtGui.QPixmap ;
QtGui.QImageReader ;
QtGui.QImageWriter ;
QtGui.QIcon ;
et QtGui.QMatrix.
VIII. Liens▲
Pour accéder à la documentation de l'ensemble des classes :
PyQt ;
PySide.
Et bien sûr, n'hésitez pas à utiliser le forum pour commenter cet article ou poser vos questions :
Forum PyQt PySide.
IX. Remerciements▲
Je tiens à remercier dourouc05Claude LELOUP et jacques_jean pour leurs conseils et impitoyable correction orthographique.