Génération aléatoire de motifs

Contenu du snippet

Ce petit script sans prétention a pour but de générer des motifs de deux sortes : des motifs dits "simples" et des motifs dits de "Escher" (en référence à certaines œuvres de Escher).

En mettant les motifs simples en mosaïque, l'espace libre entre eux sera de la même forme que chaque motif. J'ai eu l'idée de faire cet algorithme en observant les motifs de ma couette qui sont de ce type :)...

En mettant les motifs de Escher en mosaïque, on va obtenir des lignes de motifs où chaque motif est répété avec alternance des couleurs. De plus, les lignes pairs vont dans le sens inverse des lignes impaires. Le tableau "Regular Division of The Plane IV, 1957" de Escher est un bel exemple.

Le principe de génération de ces deux types de motifs est assez similaire en réalité.

N'hésitez pas à jouer avec les paramètres dans le main pour générer des images différentes. Le sens de ces paramètres est expliqué dans la documentation des méthodes de la classe Motif.

PS : il faut la bibliothèque PIL pour pouvoir générer l'image de sortie.

Source / Exemple :


#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-

import abc, random, Image, ImageDraw

__author__ = "Maxime BRIDE"
__version__ = "1.3"

class AbstractMotif(object):
	__metaclass__ = abc.ABCMeta
	
	def __init__(self, w, h):
		"""
		Initialise le motif de base.
		
		Args:
		   w (int): la largeur de la matrice.
		   h (int): la hauteur de la matrice.
		"""

		if w <= 1:
			w = 2
		elif w % 2 == 1:
			w += 1
			
		if h <= 1:
			h = 2
		elif h % 2 == 1:
			h += 1
		
		self._motif = []
		self._w = w
		self._h = h
	
	@abc.abstractmethod
	def generate(self):
		pass
	
	def draw(self, rx=1, ry=1, sf=1, c1=(255, 255, 255), c2=(0, 0, 0)):
		"""
		Dessine le motif sur une image.
		
		Args:
		   rx (int): le nombre de répétitions horizontales du motif.
		   ry (int): le nombre de répétitions verticales du motif.
		   sf (int): le facteur de redimensionnement du motif.
		   c1 (tuple): la couleur de fond du motif.
		   c2 (tuple): la couleur des pixels du motif.
		   
		Returns:
			Image. L'image issue du dessin.
		"""
		
		if rx <= 0:
			rx = 1
		if ry <= 0:
			ry = 1
			
		if sf <= 0:
			sf = 1
			
		w = rx * self._w * sf
		h = ry * self._h * sf
		
		ranrx = range(rx)
		ranry = range(ry)
		
		# Création de l'image et de l'objet de dessin.
		img = Image.new('RGB', (w, h), c1)
		draw = ImageDraw.Draw(img)
		
		# Dessin des rectangles sur l'image.
		for (i, j) in self._motif:
			for k in ranrx:
				for l in ranry:
					x = (i + self._w * k) * sf
					y = (j + self._h * l) * sf
					draw.rectangle((x, y, x + sf - 1, y + sf - 1), fill=c2)
		
		return img
		

class SimpleMotif(AbstractMotif):
	"""
	Un motif tel que défini ici est caractérisié par :
	 - une taille de N * N avec N pair et strictement positif.
	 - autant de pixels "blancs" que de pixels "noirs".
	 - le fait qu'un pixel à la position (x, y) a une couleur inverse de celle
	   du pixel à la position ((x + N / 2) % N, (y + N / 2) % N).
	"""

	def __init__(self, n=8):
		"""
		Initialise le motif de base.
		
		Args:
		   n (int): la taille de la matrice.
		"""
		
		super(SimpleMotif, self).__init__(n, n)
				
	def generate(self):
		self._motif = []
		
		pixels = [(x, y) for x in range(self._w) for y in range(self._h)]
		w2 = self._w / 2
		h2 = self._h / 2

		while len(pixels) > 0:
			p1 = random.choice(pixels)
			pixels.remove(p1)
			
			p2 = ((p1[0] + w2) % self._w, (p1[1] + h2) % self._h)
			pixels.remove(p2)
			
			self._motif.append(p1)
						

class EscherMotif(AbstractMotif):
	"""
	Un motif tel que défini ici est caractérisié par :
	 - une taille de W * H avec W et H pairs et strictement positifs.
	 - autant de pixels "blancs" que de pixels "noirs".
	 - un décalage horizontal TX (Cf. "Moins formellement").
	 - le fait que pour chaque chaque pixel P1 en (x1, x2) de couleur c1, il y
	   a 3 pixels associés :
	   - P2 en (x2 = (x1 + (W / 2)) % W, y2 = y1) de couleur c2 != c1.
	   - P3 en (x3 = ((W / 2) - x1 - TX) % W, y3 = (y1 + (H / 2)) % H) de
	     couleur c3 = c2.
	   - P4 en (x4 = (x3 + (W / 2)) % W, y4 = y3) de couleur c4 = c1.
	Moins formellement, une fois mis en mosaïque, on va obtenir des lignes de
	motifs où chaque motif est répété avec alternance des couleurs. De plus,
	les lignes paires vont dans le sens inverse des lignes impaires et en sont
	décalées de TX pixels.
	Le tableau "Regular Division of The Plane IV, 1957" de Escher est un bel
	exemple.
	"""

	def __init__(self, w=8, h=8, mw=0, mh=0, tx=0):
		"""
		Initialise le motif de base.
		
		Args:
		   w (int): la largeur de la matrice.
		   h (int): la hauteur de la matrice.
		   tx (int): le décalage des lignes impaires par rapport aux lignes
		   paires.
		"""
		
		super(EscherMotif, self).__init__(w, h)
			
		self._tx = tx
		
	def generate(self):
		self._motif = []
		
		pixels = [(x, y) for x in range(self._w) for y in range(self._h)]
		w2 = self._w / 2
		h2 = self._h / 2

		while len(pixels) > 0:
			p1 = random.choice(pixels)
			pixels.remove(p1)
			
			p2 = ((p1[0] + w2) % self._w, p1[1])
			pixels.remove(p2)

			p3 = ((w2 - p1[0] - self._tx) % self._w, (p1[1] + h2) % self._h)
			pixels.remove(p3)

			p4 = ((p3[0] + w2) % self._w, p3[1])
			pixels.remove(p4)
			
			self._motif.append(p1)
			self._motif.append(p4)
			

if __name__ == "__main__":
	SIMPLE = False
	W = 8
	H = 8
	TX = 0
	RX = 8
	RY = 8
	SF = 5
	C1 = (224, 225, 131)
	C2 = (113, 113, 0)
	OUTPUT = "motif.png"
	TYPE = "PNG"
	
	if SIMPLE:
		m = SimpleMotif(W)
	else:
		m = EscherMotif(W, H, TX)
	m.generate()
	img = m.draw(RX, RY, SF, C1, C2)
	img.save(OUTPUT, TYPE)

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.