Formation I.S.N.

Format Portable Bitmap - Exercices

Une image au format Portable Bitmap est codée ligne par ligne en partant du haut. Chaque ligne est codée de gauche à droite. Puisque chaque ligne comporte au maximum 70 caractères, il peut être très intéressant de passer à la ligne pour séparer chaque pixel plutôt qu'utiliser des espaces.
Seul le format .pbm pourrait en être exempté à condition qu'il ait une largeur inférieure ou égale à 70 pixels.

L'image ci-contre illustre la lecture linéaire (ou l'écriture) des pixels dans une image matricielle encodée sous forme de fichier texte. Pour concevoir ce type d'image, il faut donc écrire les composantes couleurs de ces pixels depuis le premier pixel situé en haut à gauche.

Les exercices ci-dessous permettent de manipuler le format Portable Bitmap dans différentes situations. Certains ont été donnés à des élèves, d'autres sont à destination du professeur qui cherche à concevoir un exercice pour ses élèves.

Création d'images

Le format .pbm est un format simple d'image matricielle en noir et blanc, c'est-à-dire formée d'un rectangle de pixels.

Une image de 10 pixels sur 10 pixels au format .pbm est tout simplement un fichier texte contenant 10 lignes, chacune de ces lignes étant constituée de 10 caractères '0' et/ou de '1'. Dans ce format, chaque '0' est interprété par un pixel blanc et chaque '1' par un pixel noir.

Voici un exemple de script Python permettant de concevoir une image au format .pbm :


##----- Variables et constantes -----##
largeur = 10
hauteur = 10

##----- Ouverture/création des Fichiers -----##
f = open('Lignes_Horizontales.pbm', 'w')

##------ En-tête ------##
f.write('P1 \n')								# P1 en ligne 1 pour déclarer le format .pbm puis passage à la ligne
f.write('{} {} \n'.format(largeur, hauteur))	# Largeur et hauteur de l'image, séparées par un espace

##------ Instructions principales ------##
for ligne in range(hauteur):
	for colonne in range(largeur):
		if ligne%2 == 0:						# Lorsque le numéro de ligne est pair
			f.write('0')
		else:
			f.write('1')
	f.write('\n')

##----- Fermeture des Fichiers -----##
f.close()

En ouvrant ce fichier avec un logiciel de visualisation d'image, on obtient l'alternance de lignes horizontales ci-contre (sans les pointillés.

En ouvrant ce fichier avec un éditeur de texte, on obtient l'affichage ci-dessous :

P1 
10 10 
0000000000
1111111111
0000000000
1111111111
0000000000
1111111111
0000000000
1111111111
0000000000
1111111111

Concevoir les programmes en Python permettant d'obtenir les images suivantes, de dimensions 150*150 :

Un carré noir Colonnes noires et blanches Croix sur fond blanc Losange noir évidé
  • Image n°1
  • Image n°2
  • Image n°3
  • Image n°4

Pour le carré noir, tout va bien :


##----- Variables et constantes -----##
largeur = 150
hauteur = 150

##----- Ouverture/création des Fichiers -----##
f = open('Carre_Noir.pbm', 'w')

##------ En-tête ------##
f.write('P1 \n')								# P1 en ligne 1 pour déclarer le format .pbm puis passage à la ligne
f.write('{} {} \n'.format(largeur, hauteur))	# Largeur et hauteur de l'image, séparées par un espace

##------ Instructions principales ------##
for ligne in range(hauteur):
	for colonne in range(largeur):
		f.write('1')
	f.write('\n')

##----- Fermeture des Fichiers -----##
f.close()

On reprend l'exemple et on teste la parité sur les numéros de colonne, avec une petite astuce supplémentaire...


##----- Variables et constantes -----##
largeur = 150
hauteur = 150

##----- Ouverture/création des Fichiers -----##
f = open('Lignes_Verticales.pbm', 'w')

##------ En-tête ------##
f.write('P1 \n')								# P1 en ligne 1 pour déclarer le format .pbm puis passage à la ligne
f.write('{} {} \n'.format(largeur, hauteur))	# Largeur et hauteur de l'image, séparées par un espace

##------ Instructions principales ------##
for ligne in range(hauteur):
	for colonne in range(largeur):
		f.write(str(colonne%2))					# On inscrit 0 lorsque colonne est pair, 1 sinon
	f.write('\n')

##----- Fermeture des Fichiers -----##
f.close()

Pour la croix, un peu de mathématiques s'imposent :

  • Un pixel est sur la diagonale «descendante» lorsque son numéro de ligne est égal à son numéro de colonne ;
  • Un pixel est sur la diagonale «montante» lorsque son numéro de ligne ajouté à son numéro de colonne donne la longueur d'un côté du carrée.

##----- Variables et constantes -----##
largeur = 150
hauteur = 150

##----- Ouverture/création des Fichiers -----##
f = open('Croix.pbm', 'w')

##------ En-tête ------##
f.write('P1 \n')								# P1 en ligne 1 pour déclarer le format .pbm puis passage à la ligne
f.write('{} {} \n'.format(largeur, hauteur))	# Largeur et hauteur de l'image, séparées par un espace

##------ Instructions principales ------##
for ligne in range(hauteur):
	for colonne in range(largeur):
		if (ligne == colonne) or (ligne+colonne == hauteur-1):
            f.write('1')
        else :
            f.write('0')
	f.write('\n')

##----- Fermeture des Fichiers -----##
f.close()

Pour le losange, ce sont les mêmes mathématiques que pour la croix, en séparant la première de la deuxième moitié des lignes.


##----- Variables et constantes -----##
largeur = 150
hauteur = 150
demi = hauteur//2

##----- Ouverture/création des Fichiers -----##
f = open('Losange_Vide.pbm', 'w')

##------ En-tête ------##
f.write('P1 \n')								# P1 en ligne 1 pour déclarer le format .pbm puis passage à la ligne
f.write('{} {} \n'.format(largeur, hauteur))	# Largeur et hauteur de l'image, séparées par un espace

##------ Instructions principales ------##
for ligne in range(demi):						# Parcours de la 1ère moitié des lignes
	for colonne in range(largeur):
		if (colonne+ligne == demi-1) or (colonne-ligne == demi-1):	# 2ème diagonale du demi-carré et 1ère diagonale
            f.write('1 \n')
        else :
			f.write('0 \n')
			
for ligne in range(demi):						# Parcours de la 2nde moitié des lignes
	for colonne in range(largeur):
		if (colonne == ligne) or (colonne+ligne == largeur-1):		# 1ère diagonale et 2nde diagonale
            f.write('1 \n')
        else :
			f.write('0 \n')

##----- Fermeture des Fichiers -----##
f.close()

Négatif d'une image

  1. Cliquer sur l'image ci-contre pour télécharger le logo Python au format .pgm.
    L'image a été «préparée» : seul un pixel est encodé en intensité de gris (le maximum est 255) à chaque ligne.
  2. Concevoir un programme permettant d'obtenir une image «négative» de ce logo.
  • Une piste
  • Une solution
Si l'intensité maximale est m, alors chaque pixel de nuance x deviendra un pixel de nuance m-x.

##----- Ouverture des Fichiers -----##
fsource = open('Logo_Python.pgm', 'r')		# Ouverture (lecture) du fichier
fresult = open('Logo_Python_neg.pgm', 'w')	# Ouverture (création) du fichier

##------ En-tête ------##
for i in range(3):                          # Les 3 lignes d'en-tête à recopier :
    fresult.write(fsource.readline())       # P2, dimensions, valeur max correspondant au blanc

##------ Instructions principales ------##
for valeur in fsource:
    fresult.write(str(255-int(valeur)))
    fresult.write('\n')

##----- Fermeture des Fichiers -----##
fsource.close()
fresult.close()

Modification de la mise en page

L'image originale du logo Python a été téléchargée sur internet (sans vérifier sa licence d'utilisation, ce qui n'est pas bien !). Elle est en couleur, au format .png et téléchargeable en cliquant sur l'image ci-contre.

Afin de ne pas rajouter de contrainte inutile aux élèves, on peut leur proposer l'image donnée à l'exercice précédent. Sinon, voici une procédure possible :

  1. Ouvrir l'image avec IrfanView (par exemple) et convertir cette image au format .pgm en sélectionnant Ascii encoding.
  2. Ouvrir l'image avec un éditeur de texte pour vérifier son en-tête (P2, dimensions, valeur max correspondant au blanc) et éventuellement supprimer les commentaires.
  3. Concevoir un programme qui lit ce fichier puis crée un nouveau fichier où un seul pixel (codé par son intensité en niveau de gris) est écrit par ligne.
  • Une solution

##----- Ouverture des Fichiers -----##
fsource = open('Logo_Python_Original.pgm', 'r')		# Ouverture (lecture) du fichier
fresult = open('Logo_Python_Modifie.pgm', 'w')		# Ouverture (création) du fichier

##------ En-tête ------##
for i in range(3):                          # Les 3 lignes d'en-tête à recopier :
    fresult.write(fsource.readline())       # P2, dimensions, valeur max correspondant au blanc

##------ Instructions principales ------##
for element in fsource:						# Chaque ligne est une chaîne de caractères...
	valeurs = element.split()               # ...transformée en liste de nombres au format "str"
	for valeur in valeurs:
		fresult.write(valeur+'\n')

##----- Fermeture des Fichiers -----##
fsource.close()
fresult.close()

Permutation circulaire

Cliquer sur l'image ci-contre pour télécharger le logo Python au format .ppm. Le fichier a été modifié pour que, sur chaque ligne, se trouve une unique composante couleur d'un pixel.
Concevoir un programme permettant d'obtenir l'image affichée ci-contre. Pour cela, le script doit permuter les composantes de chaque pixel.

  • Une solution

##----- Ouverture des Fichiers -----##
fsource = open('Logo_Python_Compo.ppm', 'r')		# Ouverture (lecture) du fichier
fresult = open('Logo_Python_Inverse.ppm', 'w')		# Ouverture (création) du fichier

##------ En-tête ------##
for i in range(3):                          # Les 3 lignes d'en-tête à recopier :
    fresult.write(fsource.readline())       # P2, dimensions, valeur max correspondant au blanc

##------ Instructions principales ------##
temp = []									# Stockage temporaire des composantes
for element in fsource:						# Chaque ligne est une chaîne de caractères
	if len(temp) == 3:
		r, v, b = temp[0], temp[1], temp[2]
		fresult.write('{0} {1} {2} \n'.format(b, r, v))
		temp = [element]
	else:
		temp.append(element)

##----- Fermeture des Fichiers -----##
fsource.close()
fresult.close()