mardi 23 juillet 2019

Chapitre 62 : affichage d’une image JPEG dans une fenêtre X11


Au chapitre 39 , nous avons vu l’affichage d’une image BMP, mais ce format n’est pas le plus utilisé. Nous allons donc écrire un programme pour afficher une image au format jpeg dans une fenêtre X11 comme nous l’avons fait pour l’image BMP. Mais je ne vais pas écrire toute la partie de décompression d’une image jpeg en assembleur quoique cela ferait un bon exercice de programmation. Je vais plutôt utiliser une librairie de fonctions déjà existante et utilisée à partir du langage C. Et de plus je vais décrire tout le processus pour utiliser une librairie quasi inconnue à partir du langage assembleur.
Une rapide recherche sur Internet montre qu’il existe plusieurs librairies de traitement d’image JPEG puis une recherche d’un package raspbian avec le mot clé jpeg retourne le package libjpeg-dev qui utilise la librairie libjpeg.
Une nouvelle recherche permet de trouver le site https://libjpeg-turbo.org/Documentation/Documentation qui donne toutes les directives pour utiliser cette librairie et en particulier donne un exemple en langage C qui va bien nous aider.
Donc nous installons sur le Raspberry le package libjpeg-dev et commençons à écrire un premier programme pour ouvrir le fichier contenant une image jpeg. Nous récupérons le nom du fichier dans la ligne de commande.
Puis à l’aide de l’exemple du programme C,nous ajoutons un premier appel à la librairie libjpeg en l’occurrence la fonction jpeg_std_error en passant en paramètre l’adresse d’une simple zone. Nous créons un nouveau script de compilation en modifiant le linker pour appeler gcc en ajoutant l’option –ljpeg.
Nous effectuons une première compilation pour vérifier si l’appel à la libraire est correcte. Ok, aucune erreur de compil.
Evidement l’exécution se termine par la fameuse erreur Erreur de segmentation . Pour corriger cela, il nous faut créer une fonction de gestion d’erreur dont nous mettons l’adresse dans le premier mot de la zone que nous passons en paramètre. J’ai trouvé l’adresse en récupérant sur le site le fichier jpeglib.h qui contient toutes les descriptions nécessaires. Hélas, les structures qui seront utilisées ne sont pas simples à décrypter !! Il faut faire un effort.
Nous ajoutons à notre programme l’instruction jpeg_create_decompress et en lui passant l’adresse d’une zone qui correspond à la structure principale de décompression cinfo. Comme la longueur est difficile à calculer nous créons une zone de 1000 caractères.
La compilation indique une erreur du linker car il ne trouve pas cette fonction dans la librairie. Donc nouveau plongeon dans le fichier jpeglib.h où nous trouvons que cette fonction est en fait jpeg_CreateDecompress à laquelle il faut passer 2 paramètres supplémentaires : la constante JPEG_LIB_VERSION dont nous trouvons la valeur 62 dans le même fichier, ce qui nous permet de créer cette constante par un .equ dans notre programme. L’autre paramètre est la longueur de la structure donc nous mettons 1000.
La compilation est cette fois ci correcte et l’exécution entraine une erreur mais la fonction est très aimable car elle indique que la longueur attendue de la structure est 464 et non pas 1000. Donc nous modifions notre programme pour lui passer 464 et cette fois ci l’exécution est correcte. Donc nous ajoutons l’instruction suivante jpeg_stdio_src qui indique la source des données à lui passant le File Descriptor de notre image et l’instruction jpeg_read_header qui va lire et charger dans la structure de décompression les informations d’entête de l’image.
La compilation est ok mais l’exécution entraine l’erreur Erreur de segmentation . je relis le programme, modifie les instructions précédentes, je tente d’autres modifications mais rien à faire l’erreur subsiste.
En désespoir de cause, je reprends l’exemple du programme en C, je l’adapte pour lire le même fichier image et je le compile sur le raspberry avec gcc. Je lance l’exécution qui se termine bien. Ceci me rassure car, l’image que j’utilise est bien OK sur le raspberry (car j’aurais pu avoir un pb de transfert entre ordinateurs) et que la libraire fonctionne bien. Je relance la compilation du programme C en demandant une sortie en assembleur puis je compare les instructions générées avec mon programme assembleur. Et je ne trouve rien qui explique cette erreur.
2 jours après ou plutôt 2 nuits après, l’idée me vient pendant la nuit que les instructions d’ouverture du fichier sont différentes. Moi j’ai utilisé le call system OPEN de linux pour ouvrir le fichier alors que le programme C utilise la fonction fopen du langage. Je modifie le programme assembleur pour appeler la fonction fopen et là miracle, plus d’erreur !!!
Après quelques tâtonnements, j’ajoute les instructions pour extraire la largeur, la hauteur et le nombre d’octets par couleur et les afficher. Tout semble ok, cela correspond bien à la taille de l’image.
Donc ajout des autres instructions pour décompresser l’image et lire une première ligne. Je pensais récupérer cette première ligne dans un buffer crée dans la section .BSS mais cela entraine l’erreur Erreur de segmentation. L’analyse de l’exemple montre qu’il faut créer le buffer en appelant la fonction alloc_sarray de la structure mem elle-même sous structure de la structure cinfo. Après une puissante analyse des structures, l’adresse de la structure mem est à +4 du début de la structure cinfo et l’adresse de la fonction alloc_sarray est à +8 du début de la structure mem. Nous calculons l’adresse finale dans le registre r4 et nous appelons la fonction par l’instruction blx r4 après avoir positionnée les paramètres demandés dans les registres r0 à r3.
L’exécution se passe bien et j’affiche le début de la  première ligne qui ne contient que des zéros !! Après cela, il faut effectuer une boucle pour extraire toutes les lignes de l’image. La boucle s’arrête lorsque le compteur de ligne contenu dans la zone output_scanline atteint la hauteur de l’image. Je détermine que le déplacement de cette zone dans la structure est de 140. Après cette boucle je complète les instructions avec l’appel jpeg_finish_decompress puis l’appel jpeg_destroy_decompress et la fermeture du fichier. Le programme affiche le début de toutes les lignes et se termine correctement.
Remarque : si dans la boucle, on ne lit pas toutes les lignes la fonction jpeg_finish_decompress signale une anomalie.
Ce premier programme nous a permis de tester les différentes fonctions et de dégrossir les structures à utiliser. Maintenant il faut afficher l’image décompressée dans une fenêtre X11. Pour cela nous reprenons l’exemple du chapitre 39 qui affichait une image BMP. Je simplifie le programme en prenant le nom du fichier image dans la ligne de commande et j’insère toutes les fonctions dans le même source.
Je supprime toutes les références à l’image BMP et j’ajoute la fonction chargeImageJpeg qui n’a qu’un seul paramètre en entrée : l’adresse du nom du fichier image. Je crée dans le fichier des structures, 2 structures qui vont permettre de remplacer les valeurs immédiates pour les adresses des largeur, hauteur et nombre d’octets par pixel par des libellés.
Je crée la structure JPEG (identique à la structure BMP) que j’alimente avec les données de l’entête image (en retour de la fonction jpeg_read_header). Et il ne reste plus qu’à écrire une fonction qui va traiter chaque ligne de l’image décompressée pour constituer une image X11 affichable. C’est le rôle de la fonction traitementLigneJpeg qui va compléter les 3 octets de couleur lus par un 4ième octet pour former une image 32 bits. Lors des tests, je me suis aperçu qu’il fallait modifier l’ordre des octets pour avoir les bonnes couleurs. Il faudra que j’approfondisse cela !!
Les nouvelles lignes sont stockées dans un nouveau buffer et comme la taille des images peut être variable, nous effectuons une allocation de place sur le tas égale à la largeur * par la hauteur * 4 octets.
Les fonctions d’affichage sont modifiées pour prendre en compte la structure jpeg
Après quelques corrections d’erreurs, le programme afficheurJPEG fonctionne correctement.