BmpReader : classe de lecture de fichier Bitmap sous MicroPython

Le monde Arduino nous à habitué à stocker des ressources graphiques directement dans du code avec un fichier header.

Par chance, MicroPython dispose d'un système de fichier... alors pourquoi pas exploiter des formats natifs comme le format BMP ?

Bibliothèque FILEFORMAT pour MicroPython
Notre GitHub de pilote MicroPython vient de recevoir un nouveau sous-répertoire nommé FILEFORMAT pour prendre en charge des format de fichiers à commencer par le format Bitmap.

Les images de test utilisées
Bon, j'aurais pu mettre une image MCHobby mais je fais également des tests avec des modules LED 8x8 d'Olimex sous MicroPython organisés en matrice pour créer un afficheur plus grand (cfr ce pilote MicroPython en cours de développement).
Pilote MicroPython FrameBuffer pour les modules LED d'Olimex
Alors l'idée à la source de FILEFORMAT, c'est qu'il semble tout à fait logique:
  1. De lire un fichier image,
  2. d'envoyer les pixels directement dans le FrameBuffer MicroPython 
  3. provoquer l'affichage du FrameBuffer sur les dalles.
Et donc vient vite la question "Quel format de fichier utiliser ?"
C'est là qu'on s'aperçoit que c'est un peu le désert! ... ou j'ai loupé la ressource du siècle pour MicroPython.
Je me suis donc mis à développer un outils, la nuit, entre 1h et 5h du matin ;-)

Le format BMP RGB888

Le but c'est de lire une ressource de type fichier et non le charger en RAM. Le fichier est en effet déjà présent en Flash (ou sur une carte SD)!
C'est ainsi que naît la classe BmpReader... pour lire les différents Pixels RGB présent dans le fichier Bitmap.

Le format BMP prévoit supporte une variante assez grande de format (en nombre de couleurs ou couleur indexée) ou de compression (sans compression, Compression RLE8 bits ou RLE4 bits).

Le format le plus simple reste encode l'encodage RGB 24 bits (8 bits par couleurs) même s'il est gourmand en espace de stockage.
Il est quand même bizarroïde ce format BMP, pour commencer, les couleurs sont codées en BGR (à l'envers) et non en RGB. Ensuite, le fichier se lit à rebours! La première ligne de l'image se trouve à la fin du fichier.
Heureusement, les pixels restent, eux, encodés de gauche à droite!

Utiliser Gimp
Il faut donc sauver ses images en couleurs 24bits.
Avec l'utilisation du logiciel Gimp, il faut exporter l'image en BMP et sélectionner les options suivantes au moment de l'export.
Encoder une image BMP en couleur 24 bits
Ceci dit, Gimp n'est pas toujours un outil pratique lorsque l'on travaille pixel par pixel (ex: pour dessiner une carte).

Utiliser mPaint
Le logiciel mPaint (dépôt Linux) est plus rudimentaire mais permet de travailler très facilement au pixel près.

A zoom important, mPaint présente une grille qui facilite singulièrement l'édition du contenu pixel par pixel.... ce qui pourrait être très intéressant pour l'édition d'image ou chaque Pixel est important. 
mPaint facilite l'édition d'une image Pixel par Pixel


Classe BmpReader

Avec la classe BmpReader (bibliothèque bmp.py), il est devient facile de lire le contenu d'une image (extraire les pixels).

from bmp import BmpReader
 
f = open( "olimex.bmp", "rb" )
bmp = BmpReader( f )

print( 'Image size (WxH) : %i x %i' % (bmp.width,bmp.height))
# Bitmap may have padding bytes in the file, so seek_pix is required before reading each line
print( '--- Line 1 ---' )
for i in range( bmp.width ):
    print( "%i : %s" % ( i, bmp.read_pix() ) )

print( '--- Line 2 ---' )
bmp.seek_pix( (0,1) ) # x=0, y=1 --> 2th line
for i in range( bmp.width ):
    print( "%i : %s" % ( i, bmp.read_pix() ) )

print( '--- Line 6 ---' )
# Move file cursor to a given pixel
bmp.seek_pix( (0,5) ) # x=0, y=5 --> 6th line
for i in range( bmp.width ):
    print( "%i : %s" % ( i, bmp.read_pix() ) )

Ce qui produit un contenu suivant avec la couleur RGB de chaque pixels:

Image size (WxH) : 24 x 91
--- Line 1 ---
0 : (0, 0, 0)
1 : (255, 255, 255)
2 : (255, 255, 255)
3 : (255, 255, 255)
...
21 : (255, 255, 255)
22 : (255, 255, 255)
23 : (255, 255, 255)
--- Line 2 ---
0 : (255, 255, 255)
1 : (0, 0, 0)
2 : (255, 255, 255)
3 : (255, 255, 255)
4 : (255, 255, 255)
...
21 : (255, 255, 255)
22 : (255, 255, 255)
23 : (255, 255, 255)
--- Line 6 ---
0 : (255, 255, 255)
1 : (255, 255, 255)
2 : (249, 160, 160)
3 : (241, 15, 15)
4 : (241, 0, 0)
...
21 : (252, 235, 235)
22 : (255, 255, 255)
23 : (255, 255, 255)

Classe ClipReader
Pouvoir accéder au contenu d'une image pixel par pixel c'est intéressant mais un peu limiter.

Ce qui serait bien, c'est d'accéder facilement à une portion de l'image... autrement dit, de faire du clipping.
Cela permet d'organiser un scrolling, sur un écran, d'une image beaucoup plus grande que le dit écran.... il suffit de déplacer le clipping entre deux affichages.
Faire du clipping + déplacer la zone sur une image = Scrolling!
Grâce au clipping il est également possible d'organiser plusieurs ressources dans une seule grande image.
Le racing-pack de kenney.nl est un excellent exemple de cette stratégie.
Utiliser le clipping pour extraire un élément d'une image ressource comme racing-pack de kenney.nl
Un exemple de clipping:

f = open( "olimex.bmp", "rb" )
bmp = BmpReader( f )

clip = ClipReader( bmp )
# Define a clipping area 
x,y,w,h = 2,2,20,16

print( "="*30 )
print( " Area x,y -> w,h = %i,%i -> %i,%i" %(x,y,w,h) )
print( "="*30 )

clip.clip( x,y,w,h  ) # 31,0,6,2 for x,y,width,height
for i in range(clip.height):
    print( "--- Clipped line %i ---" % i )
    for j in range(clip.width):
        print( "%3i : %s" %(j,clip.read_pix()) )
print( "" )
print( "--- show clipping area ---"  )
clip.show( reseek=True ) # reposition the cursor in the file

f.close()

Ce qui affiche les donnée de la zone sélectionnée.
Le méthode clip.show() permet même d'afficher le contenu de l'image (sous forme ASCII) dans la session REPL pour faciliter le débogage en cas de problème... parce qu'entre le fichier BMP et l'afficheur... la seule chose surlaquelle nous pouvons compter... c'est la session REPL!

Resultat de ClipReader.show() dans la session REPL

Aucun commentaire