vendredi 26 janvier 2018

Chapitre 17 : afficher une image au format BMP



Maintenant nous allons essayer de charger une image stockée sur la carte mémoire et l’afficher sur notre écran. Bien entendu, il n’est pas question d’utiliser une image au format jpeg, gif ou autres qui nécessitent des routines de conversion plus ou moins complexes. Nous allons nous contenter de charger une image au format .bmp car chaque pixel de l’image est retrouvé facilement. Nous allons aussi charger une image de taille réduite qui sera contenue dans notre écran, ce qui évite aussi d’écrire des routines de réduction ou de troncature ( mais les courageux pourront réfléchir aux problèmes que cela soulève ou chercher à utiliser des routines de librairies !! ).
J’ai effectué une copie de l’image en couleurs des 3 cercles issue d’un programme précèdent (voir chapitre 14) et je l’ai converti au format bmp par le logiciel Paint sous Windows. J’ai transféré l’image sur le Raspberry dans le même répertoire que le ce programme.
Dans le programme nous commençons par décrire les structures des entêtes d’un fichier image au format BMP. Un tel fichier est composé de 4 parties, une entête de fichier, une entête d’image, une description de la palette qui n’est pas obligatoire (en particulier pour le format 24 bits), et la suite des octets qui compose l’image à raison de 3 octets par pixel.
Puis dans la fonction principale, nous reprenons exactement la partie vue précédemment concernant l’ouverture et le traitement du frameBuffer en remplaçant la sous routine dessin par  chargimage.
Dans celle çi, nous ouvrons le fichier image comme nous l’avons vu dans un chapitre précèdent, nous cherchons la taille du fichier image, pour vérifier que le buffer de lecture soit assez grand pour contenir les données que nous lisons par l’appel système READ. Nous vérifions que le type de fichier est bien bmp (code hexa 4D42) et que la taille de l’image est inférieure à la taille de l’écran (sinon il faudrait écrire une routine pour tronquer l’image ….).
 Maintenant il suffit de recopier les octets du buffer de lecture vers la zone mémoire du mapping du frameBuffer pour afficher l’image. Quelques petits problèmes surgissent dus au format bmp :  1)  Nous avons 3 octets pour coder un pixel dans le fichier et 4 octets pour l’affichage (en 32 bits)   2) une ligne du fichier doit être un multiple de 4 et donc il nous faut compter des caractères supplémentaires pour être en phase à chaque ligne 3) l’image étant plus petite que l’écran, il faut ajouter à chaque ligne la différence pour rester en phase et 4) il faut commencer par la fin car l’image est inversée !! Mais tout cela fonctionne après quelques tests et nous retrouvons l’image des 3 cercles colorés.
Exercices :   Ajouter le choix du fichier image à afficher.
                      Lire une image plus grande que l’écran et la reduire (ou la tronquer) pour l’afficher
                    Gérer d’autres formats (8bits 16 bits 32 bits ) si c’est possible
                    Ou d’autres formats d’image mais c’est compliqué !!!!

lundi 22 janvier 2018

Chapitre 16 : créer une figure animée en utilisant le frameBuffer : 4



Dans ce programme, j’ai essayé d’effectuer une animation simple pour voir les difficultés que nous pouvions rencontrer. Et en effet je ne suis pas très satisfait du résultat car en utilisant une zone mémoire pour dessiner et non pas la zone directement accessible du périphérique écran, l’animation est très irrégulière. En effet la recopie de la zone mémoire vers le périphérique ne s’effectue que toutes les 0,5 secondes à ce qu’il me semble.
Mais je vous donne quand même l’exemple du programme qui consiste à faire tourner un rayon d’un cercle autour du centre. Le programme reprend le même début que les programmes précédents et nous adaptons seulement la procédure dessin. Nous commençons par peindre tout le fond de l’écran avec la routine coloriageFond puis nous allons calculer tous les points de la circonférence du cercle qui servira de base à la rotation. Pour cela nous adaptons la procèdure de dessin d’un cercle vu dans le programme précédent. En fait au lien de dessiner chaque pixel de cette circonférence, nous enregistrons la position x et y dans une table en mémoire. C’est un peu compliqué car comme nous l’avons vu, le calcul est fait pour un octant puis dupliqué pour les autres octants en changeant les signes ou les valeurs des positions x et y. Il a donc fallu retrouver l’ordre des 8 octants pour que les points enregistrés soient bien dans l’ordre c'est à dire dans le sens horaire de rotation. Pour vérifier l’exactitude des calculs, nous affichons le cercle complet par la routine balayageCercle.
Puis le programme fait appel à la routine rotationRayon qui va tracer une ligne droite entre le centre du cercle et chaque point de la circonférence. Pour animer l’image, nous dessinons le rayon en rouge puis après un temps d’attente, nous redessinons le rayon avec la couleur du fond ce qui conduit à son effacement.Remarque: le cercle de contrôle précédemment tracé s'effacera en fonction de la rotation du rayon.
Pour l’attente, j’avais dans un premier temps programmé une boucle qui décrementait un registre mais cela entrainait une consommation de 100% du processeur. Et donc dans un deuxième temps, j’ai utilisé une fonction du noyau linux  sys_nanosleep code 0x92 qui permet de gérer un temps d’attente. J’ai aussi ajouté un appel à une fonction de synchronisation code 0x90 de la portion mémoire  mappée avec le périphérique pour essayer d’améliorer l’animation. (Attention, pour des temps en secondes il faut alimenter la première zone mémoire de 4 octets des zones passées en paramètre et pour des temps en nanosecondes, il faut alimenter la deuxième zone).
Pour des temps d’attente supérieur à 0,5 seconde (500 000 000 nano secondes), l’animation est correcte mais lente. Pour des temps inférieurs, sur mon système, l’animation est saccadée et saute d’une position à l’autre.
Donc c’est pas terrible pour effectuer des animations rapides et il faudra donc trouver des solutions autres que le FrameBuffer.

Chapitre 15 : dessiner des figures avec le FrameBuffer



Nous allons continuer à utiliser le framebuffer pour dessiner diverses figures. En fait il s’agit d’utiliser la zone mémoire résultant du mapping pour renseigner les octets nécessaires avec la couleur désirée, le reste restant inchangée par rapport aux programmes précédents. Mais nous n’utiliserons que la résolution en 32 bits (soit 4 octets soit un registre) pour l’affichage.
Donc dans ce programme, nous modifierons la routine dessin pour afficher les différentes figures.  Mais tout d’abord nous préparons deux sous routines : la première  codeRGB pour concaténer les 3 couleurs rouge, vert et bleu dans le même registre de 32 bits, le 4ième octet sera ici mis toujours à zéro. La deuxième  aff_pixel_codeRGB32 placera le code couleur calculé par la précédente sur les 4 octets de la mémoire du mapping à la position X,Y correspondante de l’écran. Il faut donc multiplier la position y par le nombre de pixels contenus dans la largeur de l’écran et ajouter le nombre de pixel X puis multiplier le tour par 4 pour obtenir la correspondance en mémoire. En fait pour cette dernière opération il suffit d’utiliser l’instruction str r3,[r9,r0,lsl #2]. Vous remarquerez que je ne respecte pas de norme dans l’utilisation des registres puisqu’ici j’utilise le registre r9 dans toutes les sous procédures pour véhiculer l’adresse de début du mapping. Mais cela n’a pas beaucoup d’importance puisque toutes ces routines sont internes au programme.
Ensuite, nous écrivons toutes les routines de dessin en essayant de passer les mêmes données dans les mêmes registres (r0, r1 pour la position x,y, r2 pour la largeur de l’écran (mais que j’ai appelé malencontreusement longueur de la ligne), r3 pour le code couleur).
Voyons une des plus simples : traçage d’une ligne horizontale : traceDroiteH. En argument, nous passons les positions X de début et de fin de la droite  puis la longueur de la ligne (largeur de l’écran) la couleur et la position Y. Dans la routine, nous vérifions que la position X de fin est supérieure à la position de début puis nous effectuons une boucle qui va faire varier la position x du début à la fin et appeler la fonction d’affichage du pixel  précédente. Nous faisons la même chose pour tracer une ligne verticale sauf que nous faisons varier la position y du début à la fin.
Pour tracer une ligne quelconque, j’ai adapté et converti un algorithme de Bresenham trouvé sur internet. Vous trouverez sur Wikipédia, l’analyse complète de cet algorithme.
Pour tracer un polygone, nous créons en mémoire une série de positions X,Y de chaque sommet  et dans la routine, il nous suffira de tracer les droites qui relient ces points sans oublier de fermer le polygone. Pour le remplir, j’ai écrit une procédure au plus vite qui n’est certainement pas la bonne solution. En effet elle utilise une zone mémoire équivalent à la taille de la zone du mapping pour détecter les points se trouvant au même niveau horizontal sur les bords du polygone puis tracer des droites horizontales pour relier ces 2 points. Hélas elle fonctionne très mal pour un polygone convexe (voir le résultat du polygone vert) que l’on peut rattraper en remplissant des triangles (voir le résultat en bleu).
Pour tracer un cercle, j’ai aussi adapté et converti un algorithme trouvé sur internet. Celui çi calcule et trace les positions X,Y d’un octant (1/8 de cercle) puis par symétrie, affiche toutes les autres positions. Ces 2 derniers algorithmes minimisent les opérations arithmétiques couteuses mais je n’ai pas optimisé ces routines en assembleur ARM (pistes : meilleure utilisation des registres, réduction des push et pop, etc).
Je vais me pencher sur l’écriture d’une petite animation pour voir si ces routines sont efficaces.    
En écrivant ce post et en balayant ce programme, je me suis rendu compte que j’avais codé de nombreuses incohérences : fatigue des fêtes !!! Ce code sera certainement à reprendre pour amélioration !!!  

mercredi 3 janvier 2018

Chapitre 14 : dessiner avec le frameBuffer : II



Dans mon dernier post, je vous ai laissé un peu brutalement en vous demandant d’écrire les autres primitives pour dessiner. Je vais donc continuer de voir les possibilités de dessin en suivant toujours le site de raspberrycompote et en adaptant les programmes données en C. Aujourd’hui, nous partons d’un programme qui affiche des trames de couleurs (tiens c’était un exercice précèdent) dans les différentes résolutions possibles. Mais je vous préviens tout de suite, seuls les résultats en 32 bits par pixel sont concluants. Je ne suis pas arrivé à avoir des résultats corrects en 8 bits et 16 bits (et je n’ai pas essayé en 24 bits). Voici le source du programme en 32 bits
Nous reprenons la même démarche que précédemment pour ouvrir le périphérique /dev/fb0 et lire les données variables, mais nous modifions la résolution pour la placer en 8 bits et nous appelons la fonction système IOCTL pour écrire toute la structure variable dans les données du périphériques. Avant cette modification, nous avons recopié la structure variables dans une zone mémoire, ce qui nous permettra de restaurer ces données en fin de programme (et d’ailleurs il serait aussi bon de sauvegarder tout l’écran pour le restaurer en fin de travail).
Puis nous lisons les données fixes du périphérique et nous remarquons que la taille mémoire nécessaire au mapping (1 octet * résolution x * résolution y) est différente de celle du programme précédent (4 octets * x * y).
Nous effectuons le mapping comme précédemment puis nous écrivons 2 boucles pour balayer une partie de l’écran. La première va balayer la moitié des lignes (registre r6 position y) et l’autre va balayer chaque pixel de chaque ligne registre r0 position x). Nous devons calculer la position du pixel (x,y) dans la zone mémoire allouée par le mapping soit y * résolution de la ligne + x et calculer une couleur qui sera attribué à ce pixel. Pour cela nous multiplions la position en x par le nombre de couleur désiré (6) et nous le divisons par la résolution de la ligne. Ainsi lors du balayage les positions de x de 0 à (800 /6 soit 133) auront la couleur 0, de 133 à 266 la couleur 1 etc.
Normalement en 8 bits par pixel, le code calculé correspond à une couleur de la palette par défaut. Mais mes premiers tests n’ont rien donné (que du noir !!!!) et j’ai donc recherché sur Internet pour avoir une solution. Il semblerait que la gestion du frameBuffer sur le Raspberry n’est pas très correcte pour les résolutions 8bits, 16 bits et 24 bits : bref que seule la résolution 32 bits fonctionne correctement. Et comme le dit un intervenant pourquoi programmer des couleurs dans ces résolutions qui datent de l’ancien temps !!!!!
Donc j’ai trouvé des propositions de calcul mais cela ne me donne pas grand-chose même en changeant les dimensions x et y de l’affichage. J’ai aussi essayé en 16 bits mais là les couleurs sont vraiment bizarres et seules les couleurs en 32 bits sont correctes. Pour les courageux qui veulent tester et améliorer ces programmes, je vous donne les 2 autres sources.
Le programme se termine en écrivant les données variables qui avaient été sauvegardées en début pour remettre la bonne résolution et en fermant proprement le mapping et le périphérique (et toujours fermer une entité dans la même procédure qui l’a ouverte).
En continuant la lecture du site raspberrycompote, je me rends compte que ces problèmes d’affichage sont indiqués et que cela semble aussi dépendre des versions Linux distribuées.
Le programme suivant du site affiche 3 cercles rouge vert bleu d’un plus bel effet et donc je me laisse tenter par son écriture en assembleur (et je testerais aussi la résolution en 24 bits par pixel). Voici le source du programme.
Le programme reprend comme précédemment l’ouverture, la lecture des données fixes et variables et le mapping (c’est bien rodé tout ça maintenant) et seule la partie dessin est modifiée.  Dans cette partie, il s’agit essentiellement de calcul pour déterminer la couleur rouge (1 octet) verte (1 octet) et bleue (1 octet) de chaque pixel de l’écran en fonction de sa position. Tous les registres seront utilisés dans la procédure !! et nous serons obligés d’écrire une petite routine de calcul de racine carrée (méthode de Héron).
En fin de calcul de chaque pixel, nous appellerons 3 routines différentes suivant les résolutions 32, 24 et 16 bits.
En 32 et 24 bits, les 3 octets des couleurs seront stockés dans les 3 octets de la zone mémoire mmap et pour le 32 bits, le 4ième octet sera mis à zéro.
Pour le 16 bits, la routine s’appelle put_pixel_RGB565 car le rouge occupe les 5 premiers bits, le vert les 6 bits centraux et le bleu les 5 derniers bits (ou inversement !!!).
Voici le résultat en 32 bits :  magnifique non !!!

Pour les autres résolutions, il faut positionner l’affichage par l’instruction fbset –depth 16 ou 24 avant de lancer votre programme (vous pouvez vérifier la résolution en tapant simplement fbset). Les résultats sont curieux !! ou les routines sont erronées !!!
Exercices :  afficher l’ensemble de Mandelbrot (attention calculs en virgule flottante et c’est du boulot).
                 Afficher un cercle coloré qui change progressivement de couleur ;
                 Afficher le cercle précèdent (et couleur soleil) et lui faire décrire une courbe comme le lever et coucher du soleil d’un bord à l’autre de l’écran. Faire évoluer sa couleur ou sa luminosité.
               Si les programmes sont lents !! cherchez des solutions d’optimisation !!!