Utilisation de DevIL avec Qt
Date de publication : 01/03/2009. Date de mise à jour : 13/08/2009.
Par
Thibaut Cuvelier (Site web) (Blog)
Cet article traite de l'utilisation de DevIL comme librairie de lecture et d'écriture d'images
sans se priver des avantages de Qt.
I. Introduction
I-A. DevIL
I-B. Les plugins Qt
II. Le fichier de projet .pro
III. QImageIOPlugin
III-A. L'en-tête
III-B. Le corps
III-B-1. Le prototype
III-B-2. Constructeurs et destructeurs
III-B-3. keys()
III-B-4. capabilities()
III-B-5. create()
III-B-6. Les macros de fin
IV. QImageIOHandler
IV-A. Le constructeur
IV-B. setFormat()
IV-C. setDevice()
IV-C-1. Lump et données
IV-C-2. Palette et format
IV-D. canRead()
IV-E. read()
IV-F. write()
IV-G. imageCount(), loopCount(), nextImageDelay(), jumpToImage()
IV-H. option(), setOption(), supportsOption()
IV-I. Les surcharges pour QFile
V. L'utilisation
VI. Divers
I. Introduction
I-A. DevIL
DevIL est une librairie cross-plateforme qui s'occupe de lire et d'écrire des images.
Elle essaye par sa structure de se rapprocher de
OpenGL : trois parties la composent.
DevIL (ou IL), la partie centrale, des fonctions de très bas niveau, qui permettent
la lecture de pixels.
DevILU (ou ILU), la partie des outils de la librairie, comme la lecture
de plusieurs pixels.
DevILUT (ou ILUT), la boîte à outils, des fonctions de haut niveau qui permettent
d'effectuer facilement des opérations complexes, comme la superposition d'images.
I-B. Les plugins Qt
Il existe deux types de plugins pour Qt : ceux de haut niveau, qui étendent les fonctionnalités de Qt ;
et les bas niveau, qui étendent les fonctionnalités d'une application. Nous nous intéresserons à ceux
de haut niveau, vu que nous voulons étendre les capacités de Qt.
Les plugins sont rangés dans un répertoire
plugins. Celui-ci peut se situer à différents endroits :
dans le répertoire de votre application, dans le répertoire de Qt (s'il est installé sur la machine),
mais vous pouvez aussi désigner un ou plusieurs répertoires où les plugins sont rangés, à l'aide de
la fonction statique setLibraryPaths(), ou bien utiliser
un fichier de configuration
qt.conf.
Avant de charger un plugin, Qt fait quelques recherches pour vérifier que le plugin peut être utilisé sans
risque : il ne peut pas être compilé avec une version postérieure de Qt (pour éviter que des fonctions
inexistantes soient utilisées), ni avec une version majeure antérieure (il existe d'énormes
différences entre deux versions majeures de Qt), et, si présente, la buildkey doit être identique.
La buildkey est une clé qui identifie une compilation de Qt : deux compilateurs (ou deux versions)
ne produisent pas le même code, et cela peut engendrer des problèmes ; deux configurations de Qt différentes
peuvent produire des binaires incompatibles ; on peut préciser une buildkey lors de la configuration
de Qt : cette chaîne est concaténée aux informations précédentes, pour éviter qu'un plugin compilé pour
une application soit utilisé avec une autre.
Ce processus de validation peut être très coûteux en temps, c'est pourquoi un cache est établi. Ce cache
se base sur la date de modification du fichier (ce qui réduit les comparaisons à une seule). Si le fichier
est modifié après son entrée dans le cache, il est validé de nouveau.
II. Le fichier de projet .pro
TARGET = devil-qt
TEMPLATE = lib
CONFIG = qt plugin
VERSION = 1.0.0
HEADERS += devil-qt-plugin.hpp \
devil-qt-handler.hpp
SOURCES += devil-qt-plugin.cpp \
devil-qt-handler.cpp
target.path += $$[QT_INSTALL_PLUGINS]/imageformats
INSTALLS += target
|
III. QImageIOPlugin
Cette classe définit une interface à une classe de lecture et d'écriture (le handler, littéralement,
"celui qui s'en charge").
Cette classe ne définit pas beaucoup de fonctions à réimplémenter. C'est pourquoi cette partie du code reste
toujours très simple. En effet, elle se charge d'indiquer à Qt tous les formats et options supportés
par le handler.
III-A. L'en-tête
#include <QImageIOHandler>
#include "devil-qt-handler.hpp"
|
Une seule explication : le fichier devil-qt-handler.hpp est l'en-tête du handler, c'est lui qui
proposera le prototype des fonctions de lecture et d'écriture.
III-B. Le corps
III-B-1. Le prototype
class DevILPlugin : public QImageIOPlugin
{
public:
DevILPlugin();
~DevILPlugin();
QStringList keys ()
const;
Capabilities capabilities (QIODevice *device, const QByteArray &format)
const;
QImageIOHandler *create (QIODevice *device, const QByteArray &format = QByteArray())
const;
};
|
Toutes les méthodes sont publiques : cette classe expose, et ne fait rien.
La fonction
keys()
renvoie toutes les chaînes de caractères qui servent d'extension aux formats
(comme
.bmp,
.gif,
.png,
.jpg,
.pcx ...). Peu importe si ces formats ne sont supportés qu'en éciture ou qu'en lecture,
la fonction suivante se charge de l'indiquer.
La fonction
capabilities()
se charge d'indiquer à chaque extension de format la capacité de lecture ou d'écriture.
La fonction
create() crée une instance
du handler, qui sera utilisée par
QImage lors de la lecture
ou de l'écriture.
III-B-2. Constructeurs et destructeurs
Ce genre de classe n'a pas besoin de constructeur ni de destructeur : toutes ses fonctions
n'ont besoin de rien, si ce n'est un endroit où parler, ce que fournit Qt.
III-B-3. keys()
QStringList DevILPlugin::keys() const
{
QStringList supported;
supported << "act";
supported << "bmp";
supported << "tga";
supported << "vtf";
return supported;
}
|
Toutes les extensions sont listées. À part ça, rien de nouveau sous le soleil.
III-B-4. capabilities()
QImageIOPlugin::Capabilities DevILPlugin::capabilities
(QIODevice *device, const QByteArray &format)
const
{
QImageIOPlugin::Capabilities cap;
if (format.isEmpty()
|| !( device->isOpen() )
)
{
return cap;
}
if( format == "bmp"
|| format == "tga" )
{
cap |= CanRead;
cap |= CanWrite;
return cap;
}
else if (format == "act"
|| format == "vtf" )
{
cap |= CanRead;
return cap;
}
else
{
return cap;
}
}
|
S'il n'y a pas d'extension, ou bien que le fichier n'est pas ouvrable, on affirme que l'on ne peut rien en faire.
Si l'extension est dans la liste des supportées en écriture, on affirme qu'on sait, et la lire, et l'écrire;
Si l'extension est dans la liste des supportées en lecture uniquement, on affirme qu'on sait la lire.
Si l'extension n'est pas dans une liste, on affirme que l'on n'en rien peut faire.
III-B-5. create()
QImageIOHandler *DevILPlugin::create(QIODevice *device, const QByteArray &format)
const
{
QImageIOHandler *handler = new DevILHandler;
handler->setDevice(device);
handler->setFormat(format);
return handler;
}
|
Le plugin crée une instance du handler.
III-B-6. Les macros de fin
Q_EXPORT_STATIC_PLUGIN (DevILPlugin )
Q_EXPORT_PLUGIN2 (devil-qt, DevILPlugin )
|
La première macro prend pour paramètre le nom de la classe de plugin.
La seconde prend comme premier paramètre le TARGET du .pro, et,
comme second paramètre, le nom de la classe de plugin.
IV. QImageIOHandler
 |
J'omets ici les en-têtes et la déclaration de la classe, vu qu'elles ne diffèrent que très peu par rapport
à la partie précédente.
|
IV-A. Le constructeur
DevILHandler::DevILHandler()
{
ilInit();
iluInit();
}
|
On initialise DevIL et ILU. Le dernier ne sert que pour la gestion des effets gamma.
IV-B. setFormat()
void DevILHandler::setFormat(ILenum _Format)
{
this->I_Format = _Format;
}
void DevILHandler::setFormat(const QByteArray & format)
{
if(format == "cut")
this->I_Format = IL_CUT;
else if(format == "dcx")
else if(format == "vst")
this->I_Format = IL_TGA;
else
this->I_Format = this->I_Format = IL_TYPE_UNKNOWN;
}
|
Pourquoi deux implémentations ? La première permet d'utiliser directement un type DevIL. La seconde se base
sur les extensions, le principe de base de Qt.
IV-C. setDevice()
void DevILHandler::setDevice(QIODevice *device)
{
this->Size = device->size ();
this->Lump = (char *) malloc (Size);
ilLoadL (IL_TYPE_UNKNOWN, this->Lump, this->Size);
this->Width = ilGetInteger (IL_IMAGE_WIDTH);
this->Height = ilGetInteger (IL_IMAGE_HEIGHT);
this->Depth = ilGetInteger (IL_IMAGE_DEPTH);
this->Size_wh = QSize (this->Width, this->Height);
this->Palette = ilGetInteger (IL_PALETTE_BASE_TYPE);
this->NumImages = ilGetInteger (IL_NUM_IMAGES);
this->DurImage = ilGetInteger (IL_IMAGE_DURATION);
this->Gamma = 1.0;
this->Data = ilGetData();
if (this->Palette == IL_RGB)
this->Q_Format = QImage::Format_RGB32;
else if (this->Palette == IL_RGBA)
this->Q_Format = QImage::Format_ARGB32;
else
this->Q_Format = QImage::Format_Invalid;
}
|
On charge à partir du
QIODevice
tout ce dont nous avons besoin. Cette fonction est la seule qui agit avec ces
QIODevice.
On récupère tous les paramètres qui nous seront importants pour la suite (dans l'ordre) :
la taille (en octets), un lump, la hauteur, la largeur, la profondeur, la taille (en pixels), la palette,
le nombre d'images (image animée), la durée de chaque image (image animée), le gamma (mis à 1.0,
pour qu'il n'ait aucun effet), et les données, ainsi que le format.
IV-C-1. Lump et données
Dans DevIL, un lump est un fichier chargé en mémoire. Les données sont celles de ce lump.
Nous sommes obligés de travailler avec des lumps, car un
QIODevice
ne nous permet pas de trouver un nom de fichier : il peut être distant, ou local, ou en mémoire.
Nous devons donc charger le fichier en mémoire.
IV-C-2. Palette et format
La palette regroupe l'ensemble des couleurs gérées par le format et/ou l'image (chaque image GIF peut
avoir sa propre palette, limitée à 256 couleurs ; chaque image PSD a une seule palette).
La palette est gérée par DevIL. Son correspondant Qt est le format.
IV-D. canRead()
bool DevILHandler::canRead()
const
{
if(this->device() == 0);
{
qWarning("DevILHandler::canRead() called with no device");
return false;
}
return ilIsValidL(IL_TYPE_UNKNOWN, this->Lump, this->Size);
}
bool DevILHandler::canRead(QIODevice *device)
{
if (!device)
{
qWarning("DevILHandler::canRead() called with no device or an unusable device");
return false;
}
this->setDevice(device);
return ilIsValidL(IL_TYPE_UNKNOWN, this->Lump, this->Size);
}
|
La logique est la même : on vérifie qu'un
QIODevice
est bien chargé (s'il est précisé, on le charge). Puis, on demande à DevIL la validité du fichier.
IV-E. read()
bool DevILHandler::read (QImage *image)
{
this->Image = QImage (this->Size_wh, this->Q_Format);
this->Image.fromData(this->Data, this->Size, 0);
return true;
}
|
On crée un
QImage
avec les données dont nous disposons, puis nous y injectons les données.
IV-F. write()
bool DevILHandler::write (const QImage & image)
{
if ( ilSaveL(this->I_Format, this->Lump, 0) == 0)
return false;
else
return true;
}
|
On sauve dans le lump, tout simplement.
IV-G. imageCount(), loopCount(), nextImageDelay(), jumpToImage()
int DevILHandler::imageCount()
const
{
if(this->I_Format != IL_GIF)
return 0;
else
return this->NumImages;
}
bool DevILHandler::jumpToImage(int imageNumber)
const
{
return false;
}
|
Si le format est bien animé (parmi les formats supportés par DevIL, le seul animé que je connaisse
est le GIF), on renvoie la variable membre correspondante.
DevIL ne supporte pas d'aller directement à une certaine image : nous devons donc renvoyer un résultat
qui sanctionne cette incapacité.
IV-H. option(), setOption(), supportsOption()
QVariant DevILHandler::option(QImageIOHandler::ImageOption option)
const
{
if(option == QImageIOHandler::Size)
return this->Size_wh;
else if (option == QImageIOHandler::Gamma)
return this->Gamma;
else if (option == QImageIOHandler::Animation)
if(this->I_Format == IL_GIF)
return true;
else
return false;
else
return false;
}
void DevILHandler::setOption(QImageIOHandler::ImageOption option, const QVariant & value)
{
if (option == QImageIOHandler::Gamma)
{
this->Gamma = value.toDouble();
iluGammaCorrect(this->Gamma);
}
}
bool DevILHandler::supportsOption(QImageIOHandler::ImageOption option)
const
{
if(option == QImageIOHandler::Size)
return true;
else if (option == QImageIOHandler::Gamma)
return true;
else if (option == QImageIOHandler::Animation)
return true;
else
return false;
}
|
option() renvoie le paramètre actuel
de l'option en question.
setOption()
met l'option à la valeur désirée. Toutes les options ne peuvent pas être modifiées (une image statique ne peut pas
devenir animée, et l'inverse n'est pas beaucoup plus possible).
IV-I. Les surcharges pour QFile
DevIL est principalement prévu pour lire à partir de fichiers, et non de la mémoire. C'est pourquoi
on peut aussi implémenter des fonctions spécialisées dans le traitement de fichiers, avec des
QFile.
void DevILHandler::setDevice(QFile *device)
{
this->fDevice = device;
this->fileName = this->fDevice->fileName().toStdString().c_str();
ilLoad (IL_TYPE_UNKNOWN, this->fileName);
}
|
Voici les seules lignes à commenter. Le pointeur pour le
QFile ne peut pas être Device,
vu qu'il est réservé aux
QIODevice.
On utilise donc fDevice.
La seule ligne un peu compliquée de tout le code prend l'objet sur lequel fDevice pointe, prend le nom de
fichier, le convertit en std::string puis en char, type hérité du C,
langage dans lequel DevIL est écrit.
Ensuite, on doit utiliser la fonction ilLoad(), qui prend en paramètre le type (inconnu)
et le nom de fichier précédemment trouvé.
Ces modifications se trouvent dans toutes les autres surcharges pour
QFile.
 |
Il n'est pas prévu de base que les plugins puissent s'occuper directement des fichiers,
il sont sensés passer par des QIODevice, comme les plugins de base de Qt. L'utilisation directe de fichiers
par QImage sera donc limitée à ce plugin.
|
V. L'utilisation
Il faut préciser où se situe le plugin :
QCoreApplication::addLibraryPath("repertoire/des/plugins");
|
Correspondra au répertoire repertoire/des/plugins/imageformats pour ce type de plugins.
Et puis c'est tout !
QImage
pourra désormais utiliser DevIL. Pas toutes ses fonctionnalités, seulement celles que
QImageIOHandler supporte.
Par défaut, Qt ira chercher ce type de plug-ins dans le dossier imageformats de votre
application.
Pour pouvoir utiliser ce plug-in, vous devez avoir installé DevIL : soit avec l'un des paquets de binaires
fournis sur le site, soit avec l'un des paquets de votre distribution, soit compilé à la main. Vous devrez aussi,
soit mettre sa librairie dans le répertoire du plug-in, soit la mettre dans un répertoire accessible par le
PATH, soit, enfin, mettre un lien symbolique vers la librairie dans le dossier du plug-in. Si vous ne le faites pas,
ce genre de message pourra s'afficher (dans une console sur GNU/Linux, par exemple).
symbol lookup error: /home/stage1/Desktop/imageformats/libdevil-qt.so: undefined symbol: ilInit
|
VI. Divers
Vous pouvez jeter un coup d'œil à ces quelques sites pour plus d'informations sur DevIL et les plugins de Qt.
Les sources compilent parfaitement et sans avertissement sur ces environnements :
- DevIL 1.7.7 et 1.7.8
- Qt 4.4.3, 4.5.0, 4.5.1
- Visual Studio 2008 9.0.20729.SP1
- Intel C++ Compiler 10.1.694.2008 et 11.0.759.2008
Un tout grand merci à Ikipou, pour ses encouragements, et à yan, pour les idées !
Sans oublier ram-0000, sans qui bien des fautes seraient restées !


Les sources présentées sur cette page sont libres de droits
et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation
constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ©
2009 Thibaut Cuvelier. Aucune reproduction,
même partielle, ne peut être faite de ce site et de l'ensemble de son contenu :
textes, documents, images, etc. sans l'autorisation expresse de l'auteur.
Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 €
de dommages et intérêts.
Cette page est déposée.