Programmation objet et géométrie/Objets en Python sous Gimp/Manipulation de pixels en Python sous Gimp

Un livre de Wikilivres.
Sauter à la navigation Sauter à la recherche


Dans les exemples ci-dessous, on ne va pas modifier de pixels existants (principe du filtre photo) mais créer des images. Les filtres seront donc des filtres de rendu, et c'est parmi les filtres de rendu qu'on va les placer.

Pour éviter d'alourdir les scripts, on ne leur demande pas de créer l'image en la dimensionnant et d'y ajouter un calque (l'image et le calque sont des objets) mais on va créer une image sous Gimp, ce qui permet de choisir ses dimensions et la dote automatiquement d'un calque. Ces lignes économisées en Python vont d'ailleurs faire intervenir les deux objets image et calque par lecture de leurs propriétés (par exemple leur taille) sous Python. Dans le langage de la programmation objet, on dira que l'instanciation est faite par l'utilisateur sous Gimp et non par Python.

Fonction de deux variables[modifier | modifier le wikicode]

Comme les coordonnées sous Gimp sont entières, on peut représenter graphiquement le pgcd de ces coordonnées comme une image 2D. La première ligne sert à expliquer au moteur Gimp que c'est un filtre Python qu'on met en place:

#!/usr/bin/env python


import math
from math import *
from gimpfu import *

Et pendant qu'on y est, on importe les fonctions mathématiques et l'extension gimpfu (les trois dernières lignes ci-dessus).

Algorithme d'Euclide en Python[modifier | modifier le wikicode]

L'algorithme d'Euclide pour calculer un pgcd peut se résumer à la description suivante:

Théorème: Le pgcd de a et b est égal au pgcd de b et a modulo b.

On peut aussi le décrire impérativement par l'algorithme suivant:

  • Remplacer a par b et simultanément b par a modulo b
  • Recommencer jusqu'à ce que b divise a
  • Alors b est le pgcd des deux nombres.

Python permet aisément d'affecter simultanément deux variables, ce qui raccourcit considérablement le code (en plus, le test de non-divisibilité de a par b revient au calcul du reste euclidien de a par b, interprété booléennement comme faux par Python tant qu'il est non nul):

def gcd(a,b):
	while(a%b):
		a,b=b,a%b
	return b

Création du filtre[modifier | modifier le wikicode]

Pour éviter d'avoir à créer et dimensionner l'image sous Python, on va la considérer comme une variable d'entrée du script (le filtre). Comme on le voit plus bas dans la partie enregistrement du script, l'image est un objet Python appelé ici img (c'est une variable locale). Le script s'appelle python_pgcd et il est constitué d'une fonction portant non sur des nombres, mais sur des images. Que fait donc ce script?

  1. Il commence par récupérer deux des propriétés de l'objet image: Sa largeur et sa hauteur, qui sont des entiers;
  2. Ensuite il récupère son premier calque (normalement le seul à être présent). img.layers est la liste des calques de l'objet img fournie à Python sous forme d'un tableau, et img.layers[0] est donc le premier calque.
  3. Ensuite le script parcourt une double boucle sur l'abscisse x et l'ordonnée y. Pour éviter que l'abscisse ou l'ordonnée soit nulle (et dans ce cas l'algorithme d'Euclide ne marche pas), on leur ajoute 1, et on calcule le pgcd de ces deux nombres. Comme les niveaux de couleurs des pixels sont nécessairement inférieurs à 256, on prend le reste de la division euclidienne du pgcd g par 256 (qui est donc entre 0 et 255);
  4. On crée une couleur Gimp (un triplet d'entiers), où le niveau de rouge est égal au pgcd g; les niveaux de vert et de bleu sont nuls.
  5. Enfin on colorie le pixel de coordonnées x et y avec la couleur calculée, en appelant pour cela la méthode set_pixel de l'objet calque (le premier calque de l'objet img).
def python_pgcd(img) :
	largeur=img.width
	hauteur=img.height
	calque=img.layers[0]
	for x in range(largeur):
		for y in range(hauteur):
			g=gcd(x+1,y+1)%256
			couleur=(g,0,0)
			calque.set_pixel(x,y,couleur)

Enregistrement dans la base de données[modifier | modifier le wikicode]

Pour pouvoir tester ce filtre, il est donc nécessaire de l'enregistrer dans la base de données de Gimp (avant de lancer celui-ci). Ce qui se fait en entrant un texte de ce genre en bas du script:

register(
    "pgcd",
    "pgcd de x et y",
    "rendu du pgcd",
    "Auteur du script",
    "Licence GPL",
    "2010",
    "calcul des pgcd...",
    "*",
    [
    (PF_IMAGE,"img","image vide",None)
    ],
    [],
    python_pgcd,
        menu="<Image>/Filters/Render")

Le texte entre crochets signifie que la fonction python_pgcd dont le nom figure juste au-dessous, a une variable (puisque la liste de ses variables ne contient qu'une entrée!), que celle-ci est du type PF_IMAGE, qu'elle s'appelle "img", que son titre est "image vide" et qu'elle n'a pas de valeur par défaut.

Pour finir d'enregistrer le filtre, il reste à dire où. Et ci-dessus, c'est dans le sous-menu des rendus, lui-même dans le menu des filtres.

Pour finir le script, il reste un appel à la fonction main():

main()

Le tout doit être enregistré dans un fichier texte ayant l'extension .py, dont le bit exécutable doit être positionné, et placé dans le dossier des filtres Gimp.

Effet produit[modifier | modifier le wikicode]

Lorsqu'on met au point un filtre Gimp, il est vivement conseillé de lancer le Gimp dans une console, celle-ci affichant les (inévitables) erreurs de mise au point, ce qui aide à voir ce qui ne va pas (déboguer...). Une fois le Gimp lancé, et une image créée en 400 fois 400 pixels, on devrait retrouver le script dans le menu Rendus du Gimp, et une fois celui-ci lancé, au bout de quelques secondes, on devrait voir l'image suivante:

Pgcd.png

Utilisation du hasard pour créer une fractale[modifier | modifier le wikicode]

Un autre exemple: Le triangle de Sierpinski, construit itérativement comme un IFS. Le principe est assez simple mais non déterministe:

  1. On lance un dé à trois faces 1, 2 et 3 (impossible à faire en vrai sauf en remplaçant les 4, 5 et 6 d'un dé normal par des 1, 2 et 3; mais facile à simuler).
  2. Si le dé tombe sur 1, on ajoute à la figure le milieu de [AM]; sinon:
    1. S'il tombe sur 2, on ajoute à la place, le milieu de [BM]; sinon
    2. s'il tombe sur 3, on ajoute le milieu de [CM].
  3. On recommence quelques milliers de fois.

Alors, pour tout choix initial de M, le nuage de points ainsi construits se rapproche du triangle de Sierpinski. Pour mieux montrer l'effet du hasard, on donne à M une couleur rouge, verte ou bleue selon le résultat du dé.

Pour avoir un nombre pseudoaléatoire égal à 1, 2 ou 3, on écrit en Python

de=ceil(3*random())

mais cela nécessite qu'on charge préalablement le module random par

from random import *

Pour raccourcir encore le code, on peut définir une fonction Python pour calculer les coordonnées du milieu d'un segment. Voici le filtre complet:

#!/usr/bin/env python


import math
from math import *
from random import *
from gimpfu import *

def milieu(m,n):
	return ((m[0]+n[0])/2,(m[1]+n[1])/2)

def python_sierpinski(img) :
	largeur=img.width
	hauteur=img.height
	calque=img.layers[0]
	A=(largeur/2,0)
	B=(0,hauteur)
	C=(largeur,hauteur)
	M=(largeur/2,hauteur/2)
	for i in range(100000):
		de=ceil(3*random())
		if de==1:
			M=milieu(A,M)
			couleur=(255,0,0)
		elif de==2:
			M=milieu(B,M)
			couleur=(0,255,0)
		else:
			M=milieu(C,M)
			couleur=(0,0,255)
		x=M[0]
		y=M[1]
		calque.set_pixel(x,y,couleur)




register(
       	"sierpinski",
	"ifs en couleurs",
	"dessin du triangle",
	"Auteur du script",
	"Licence GPL",
	"2010",
	"Sierpinski",
	"*",
	[
	(PF_IMAGE,"img","image vide",None)
	],
	[],
	python_sierpinski,
        menu="<Image>/Filters/Render")

main()

En appliquant ce filtre à une image vide de 400 pixels par 400, on obtient la figure suivante:

SierpinskiGimp1.png

Cette image est à comparer avec la version JavaScript créée avec ImageJ dans le chapitre précédent.

Triangle chromatique[modifier | modifier le wikicode]

Pour ce dernier exemple, on va dessiner point par point un triangle, les quantités de rouge, vert et bleu étant les coordonnées barycentriques du pixel dans le triangle. Les sommets du triangle sont A(0,hauteur) qui est tout en bas à gauche (en effet l'axe des ordonnées est dirigé vers le bas), B(largeur,hauteur) qui est en bas à droite et C=(largeur/2,0) qui est donc en haut au milieu. La première boucle est celle sur les ordonnées, qui réalise donc un balayage ligne par ligne (comme dans un écran télé) de haut en bas. Dans cette boucle, on calcule la demi-largeur qui est . Mais comme y est un entier, on veut que Python le considère comme réel pour que la division des réels lui soit appliquée. On le convertit donc en réel en lui additionnant 0.0. De même, la division par 2 est celle par 2.0 pour indiquer que c'est bien par un réel qu'on divise. Mais on va boucler de à , ce qui nécessite donc que demilargeur soit un entier! Donc on l'arrondit, ce qui donne

demilargeur=int((y+0.0)/hauteur*largeur/2.)

Pour la quantité de bleu, c'est facile, elle est proportionnelle à 1-y (en allant de 0 à 255). Donc les quantités de rouge et de vert s'additionnent à une quantité proportionnelle à y. Cette variable est multipliée par une autre variable appelée rv (pour rouge-vert) pour avoir la quantité de vert, et par 1-rv pour avoir la quantité de rouge. Ceci illustre l'associativité des barycentres. Donc . Mais pour convertir x en réel, on ajoute 0.0 au numérateur,et pour éviter le risque de diviser par 0, on ajoute un nombre petit mais positif au dénominateur:

rv=(x-largeur/2+demilargeur+0.0)/2/(demilargeur+0.000001)

Finalement le script complet donne ceci:

#!/usr/bin/env python


import math
from math import *
from gimpfu import *


def python_couleurs(img) :
	largeur=img.width
	hauteur=img.height
	calque=img.layers[0]
	A=(0,hauteur)
	B=(largeur,hauteur)
	C=(largeur/2,0)
	for y in range(hauteur):
		demilargeur=int((y+0.0)/hauteur*largeur/2.)
		for x in range(largeur/2-demilargeur,largeur/2+demilargeur+1):
			rv=(x-largeur/2+demilargeur+0.0)/2/(demilargeur+0.000001)
			rouge=int((y+0.0)/hauteur*255*(1-rv))
			vert=int((y+0.0)/hauteur*255*rv)
			bleu=int((1-(y+0.0)/hauteur)*255)
			couleur=(rouge,vert,bleu)
			calque.set_pixel(x,y,couleur)




register(
       	"couleurs",
	"triangle chromatique",
	"dessin du triangle",
	"Auteur du script",
	"Licence GPL",
	"2010",
	"Triangle des couleurs",
	"*",
	[
	(PF_IMAGE,"img","image vide",None)
	],
	[],
	python_couleurs,
        menu="<Image>/Filters/Render")

main()

En appliquant ce filtre à une image vide de 400 pixels par 400, on obtient le triangle des couleurs que voici:

ChromaticTriangleGimp1.png

Un peu d'antialiasing ne ferait pas de mal (dans Filtres>Amélioration>Lissage)...

Ensembles de Julia et de Mandelbrot[modifier | modifier le wikicode]

Utiliser les mêmes techniques pour construire les ensembles de Julia et de Mandelbrot est laissé en exercice. En effet

  1. Le cas JavaScript est traité dans le chapitre précédent ;
  2. Gimp possède déjà un filtre créant ces ensembles, et celui-ci est interactif.