Archive

Archive for the ‘Python’ Category

Histogramme cumulé de la répartition des cartes du jeu The City avec matplotlib

The city est un jeu de cartes où chaque joueur pose des bâtiments, représentés par des cartes. Elles ont chacune un coût (compris entre 0 à 11), génère un revenu et des points de victoires. La plupart d’entre elles existent en plusieurs exemplaires. Certaines cartes possèdent aussi des symboles (fontaine, caddie et voiture) qui influencent le revenu ou les points gagnés.

Une carte du jeu

La répartition de ces symboles n’est pas uniforme comme on peut le constater sur l’histogramme suivant :
Répartition des cartes

Le graphique a été réalisé avec matplotlib, une bibilothèque python pour créer des graphiques 2D ou 3D. La façon de le réaliser sera abordée dans une seconde partie, la première étant consacrée à quelques remarques mise en évidence par le graphique.

Interprétation et limitation

Quelques faits mis en lumière par l’histogramme :

  • Plus les cartes coûtent cher, plus elles sont rares. Les cartes les plus courantes sont souvent en plusieurs exemplaires alors que les dernières sont uniques. Cependant certaines des moins onéreuses ne peuvent être joués qu’en un seul exemplaire.
  • Une stratégie Fontaine-Voiture semble plus difficile que Fontaine-Caddie ou Caddie-Voiture car le nombre de cartes ayant les deux couleurs ensemble est bien plus faible.
  • La carte Caddie la plus forte vaut 7, alors que pour les deux autres couleurs c’est 11. Plus coûteux, mais plus puissant…

Le graphique a aussi des limites :

  • La stratégie sans couleur ne semble pas pertinente alors que les Villas (sans couleur) valent 4 et ont une très bonne synergie entre elles.
  • Le graphique compte le nombre de cartes, mais certaines cartes de la moitié supérieure peuvent avoir plusieurs fois le même symboles (maximum trois). Le graphique donne donc une idée de la probabilité d’obtenir une carte avec le symbole donné, pas la probabilité du nombre de symboles obtenus.

Réalisation

L’interface de programmation ressemble à Pychart, une autre bibliothèque python pour faire des graphiques, présentée dans un article précédent. (Entre les deux, préférez matplotlib. (Oui, j’aime bien les parenthèses. (Pas vous ?)))

Par défaut, la sortie du graphique est dans une fenêtre de l’environnement de bureau. C’est pratique lorsqu’on fait des tests avec l’interpréteur python. C’est prévu pour, il existe même une option pour avoir directement matplotlib chargé au démarrage avec ipython (ipython --pylab).
Il est possible de changer la sortie au démarrage du script, à condition de le faire avant d’importer pyplot.
Pour avoir une sortie en PNG :

import matplotlib
matplotlib.use('Agg') # au début du script
from matplotlib import pyplot as plt

# ...
# plein de code malin et très lisible
# ...

plt.savefig("/tmp/thecity-histogramme.png")

Les données sont fournies dans un n-uplet représentant la distribution d’une valeur sur l’ensemble des barres. Par exemple, pour représenter la combinaison Fontaine-Voiture-Caddie (notée BVO) :

BVO = (3, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0) # répartition de la caractéristique BVO
p1 = plt.bar(index, BVO, WIDTH, color="#D70751") # représentation dans le graphique

C’est comme si on empilait les couches de données les unes au-dessus des autres. Pour les deuxièmes caractéristiques suivantes, il faut ajouter un paramètre bottom qui fournit la hauteur de départ pour la nouvelle couche.

p2 = plt.bar(index,
             BO,
             WIDTH,
             color="#D217F4",
             bottom=BVO)

Comme la hauteur sera différente pour chaque valeur, j’ai ajoutée une fonction qui fait la somme des listes pour la troisième couche et supérieures. C’est du code Python générique, donc utilisable dans n’importe quel autre contexte :

import itertools
def sum_cards(cards):
    sum_cards_by_value = lambda x, y: map(sum, itertools.izip(x, y))
    return reduce(sum_cards_by_value, cards)

Comportement :

In [4]: sum_cards([[1, 2, 3], [10, 20, 30]])
Out[4]: [11, 22, 33]

In [5]: sum_cards([[1, 2, 3], [10, 20, 30], [100, 200, 300]])
Out[5]: [111, 222, 333]

Le reste du code reste compréhensible simplement en le lisant ; il est fourni en fin de l’article.

À partir de matplotlib 1.2.0, un graphique pour des données continues est aussi disponible. Il est nommé stackplot (voir l’exemple fourni par matplotlib).

Sources

La boîte de jeu, amenée par Arthur, avec laquelle on joue dans la boîte.

Prise en main de matplotlib

Un exemple d’histogramme fourni par matplotlib

Script de génération de l’histogramme :

#! /usr/bin/env python
# -*- encoding: utf-8  -*-

import itertools
import matplotlib
matplotlib.use('Agg')

import numpy as np

from matplotlib import pyplot as plt


BVO = (3, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0)
BO  = (0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0)
BV  = (0, 4, 2, 5, 0, 0, 0, 1, 0, 0, 0, 0)
VO  = (0, 7, 5, 0, 2, 0, 1, 0, 1, 1, 0, 0)
B   = (0, 7, 0, 4, 1, 3, 2, 1, 3, 1, 0, 1)
V   = (0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0)
O   = (0, 0, 2, 3, 3, 0, 1, 0, 1, 0, 0, 1)
no  = (0, 13, 11, 0, 8, 0, 0, 0, 0, 0, 0, 1)

WIDTH = 0.55

def max_sum():
    return max([
        BVO[i] + BO[i] + BV[i] + VO[i] + B[i] + V[i] + O[i] + no[i]
        for i
        in range(data_length())])

def data_length():
    return len(BVO)

def higher_card_cost():
    return data_length() - 1


index = np.arange(data_length())

def sum_cards(cards):
    sum_cards_by_value = lambda x, y: map(sum, itertools.izip(x, y))
    return reduce(sum_cards_by_value, cards)


p1 = plt.bar(index, BVO, WIDTH, color="#D70751")
p2 = plt.bar(index, BO, WIDTH, color="#D217F4", bottom=BVO)
p3 = plt.bar(index, BV, WIDTH, color="#0ED5D3", bottom=sum_cards([BVO, BO]))
p4 = plt.bar(index, VO, WIDTH, color="#E7F417", bottom=sum_cards([BVO, BO, BV]))
p5 = plt.bar(index, B, WIDTH, color="#0101f3", bottom=sum_cards([BVO, BO, BV, VO]))
p6 = plt.bar(index, V, WIDTH, color="#2fc62b", bottom=sum_cards([BVO, BO, BV, VO, B]))
p7 = plt.bar(index, O, WIDTH, color="#F3B439", bottom=sum_cards([BVO, BO, BV, VO, B,  V]))
p8 = plt.bar(index, no, WIDTH, color="#999999", bottom=sum_cards([BVO, BO, BV, VO, B, V, O]))


plt.ylabel(u"Quantité")
plt.xlabel(u"Coût")
plt.title("Distribution des cartes de The City")
plt.xticks(index+WIDTH/2., [str(i) for i in range(data_length())])
plt.yticks(np.arange(0, (max_sum() + 1), 10))
plt.legend((p1[0], p2[0], p3[0], p4[0], p5[0], p6[0], p7[0], p8[0]),
           ('Fontaine, Caddie, Voiture (BVO)', 'Fontaine, Voiture (BO)',"Fontaine, Caddie (BV)", "Caddie, Voiture (VO)", "Fontaine (B)", "Caddie (V)", "Voiture (O)", "Aucune couleur"))

plt.savefig("/tmp/thecity-histogramme.png")
Catégories :Python Étiquettes : ,

Modifier une image BMP avec Construct (et sans PIL)

Le module le plus utilisé pour manipuler des images en Python est probablement PIL (ou Pillow pour les utilisateurs de pypi). Construct est un module pour analyser et contruire des fichiers binaires. La définition des fichiers binaires doit être faite pour chaque format que l’on souhaite traiter. Cependant, certains formats de fichiers binaires sont déjà définis, comme les formats elf, ext2, bmp, gif, etc. (« etc » n’étant pas un format binaire mais juste l’abréviation de et caetera.) Les objectifs des deux modules sont différents donc il ne faut pas espérer remplacer l’un par l’autre.

L’objectif de l’article est faire une rapide présentation de Construct en changeant la couleur de certains pixels d’une image au format .bmp.

Remplacement des pixels blancs par des pixels noirs

Remplacement des pixels blancs par des pixels noirs

Installer Construct

Pour contruire un virtualenv et installer construct :

$ virtualenv democonstruct
$ ./democonstruct/bin/pip install construct

Lire un fichier

La méthode de base est parse() :

import construct.formats.graphics.bmp
with open("/tmp/python_fond_blanc.bmp") as f:
    content = f.read()
obj = construct.formats.graphics.bmp.bitmap_file.parse(content)

Il existe aussi une méthode plus courte pour faire l’équivalent :

obj = construct.formats.graphics.bmp.bitmap_file.parse_stream(open("/tmp/python_fond_blanc.bmp"))

L’objet analysé dispose en attributs des données extraites du fichier :

Container({'palette': <construct.lib.container.LazyContainer object at 0xe23fc8>,
           'vertical_dpi': 2835,
           'image_data_size': 20160,
           'compression': 'Uncompressed',
           'planes': 1,
           'bpp': 24,
           'signature': 'BM',
           'important_colors': 0,
           'header_size': 40,
           'height': 80,
           'width': 84,
           'version': 'v3',
           'data_offset': 54,
           'file_size': 20214,
           'number_of_pixels': 6720,
           'horizontal_dpi': 2835,
           'pixels': <construct.lib.container.LazyContainer object at 0xdea7e0>,
           'colors_used': 0})

Ici, le fichier est encodé selon la version 3 du format, fait 84 pixels de large et 80 pixels de hauts, etc. Par contre, les pixels ne sont pas directement accessibles mais le sont par l’intermédiaire d’un LazyContainer, un conteneur paresseux dans le but de ne pas charger en mémoire des données qui pourraient être énormes (par exemple une photo à taille réelle d’une grosse baleine en surpoid).
Pour accéder aux pixels, il suffit de faire :

>>> obj.pixels
<construct.lib.container.LazyContainer object at 0x287c7e0>
>>> obj.pixels.value
[[[255, 255, 255], [255, 255, 255], #ça continue encore et encore...
...                                 #c'est que le début, d'accord, d'accord
, [255, 255, 255], [255, 255, 255], [255, 255, 255]]]

obj.pixels.value est une liste de lignes. Chaque ligne est une liste de pixels. Chaque pixel est une liste de 3 entiers compris entre 0 et 255 (rouge, vert et bleu codées sur un octet).

Modifier un fichier

Il est possible de construire un fichier binaire en passant un objet en paramètre de la méthode build(). L’objet peut être défini complètement ou obtenu en modifiant un objet existant préalablement. Dans l’exemple ci-dessous, on va changer les pixels blancs (défini par [255, 255, 255]) en noirs (défini par [0, 0, 0]).

def blanc_vers_noir(src_pixels):
    BLANC = [255, 255, 255]
    NOIR = [0, 0, 0]
    dst_pixels = []
    for index_ligne, ligne in enumerate(src_pixels):
        dst_pixels.append([])
        for pixel in ligne:
            if pixel == BLANC:
                dst_pixels[index_ligne].append(NOIR)
            else:
                dst_pixels[index_ligne].append(pixel)
    return dst_pixels

conversion = blanc_vers_noir(obj.pixels.value)
obj.pixels = conversion

with open("/tmp/python_fond_noir.bmp", "w") as f:
    f.write(construct.formats.graphics.bmp.bitmap_file.build(obj))

La nouvelle image est enregistrée avec les pixels transformés. À noter que les yeux ont aussi été convertis et que le détourage du logo est mal fait car la valeur n’est pas celle d’un blanc pur.

Utilité et limites de Construct

S’il s’agit de modifier des images, PIL fait probablement mieux l’affaire car il s’agit d’une bibliothèque de plus haut niveau (par exemple, et en toute objectivité, le meilleur exemple du monde). La seule utilité que j’y vois serait pour générer automatiquement des dégradés ou des figures géométriques simples.

D’un point de vue plus général, Construct permet de décrire par une structure Python un format de données binaires pour naviguer et modifier plus facilement des fichiers. L’utilisation qui en est faite ici est surtout à but de démonstration. S’il est possible de s’en servir pour naviguer dans des fichiers texte, d’autres bibliothèques seront plus adaptées (BeautifulSoup ou lxml pour du HTML ou XML).

Construct devrait surtout servir à décrire les fichiers binaires à manipuler dans une application. C’est faisable en créant une nouvelle classe décrivant la structure binaire du fichier. Les méthodes parse() et build() sont alors disponible automatiquement pour la nouvelle classe.

Catégories :Python Étiquettes : ,

Évolution du nombre de messages sur debian-l10n-fr et debian-users-fr avec pychart

Pychart est une bibliothèque Python permettant des graphiques directement en python. Un paquet Debian est disponible (nommé python-pychart) avec la dernière version 1.39 (qui date de 2006).

Debian utilise massivement des listes de diffusion pour la collaboration de ses membres. Parmi les nombreuses listes existantes, trois sont francophones :

  • debian-users-fr, dédiée aux questions des utilisateurs ;
  • debian-l10n-fr, dédiée aux traductions en français ;
  • debian-news-french, dédiée aux communiqués officiels. Elle est exclue du graphique car elle ne représente pas vraiment l’activité des listes de diffusion.

Pychart est utilisé pour créer un graphique montrant l’évolution du volume de messages postés sur ces deux listes pendant une année glissante (soit d’avril 2012 à avril 2013).

Le graphique

Le graphique représente le nombre de messages électroniques envoyés sur les deux listes par jour.

Volume des messages sur les listes de diffusion Debian francophones

On constate que les pics et les creux se font sur les mêmes mois. Par contre, les tendances générales sont inversées : en hausse pour la liste utilisateur, en baisse pour la liste dédiée aux traductions.

Réalisation

Le code Python est fourni à la fin de l’article. Pychart s’appuie sur de nombreuses inventions anciennes mais solidement éprouvées :

Invention de la poubelle (1884)

Toute la documentation fait des imports globaux (from pychart import *), comme beaucoup d’exemples disponibles sur le web. Pour éviter de polluer l’espace de nom, il suffit d’importer les différents sous-modules de manière classique (import pychart.sousmodule). Je n’ai pas eu besoin d’importer certains sous-modules bien qu’ils soient utilisés dans le script.

Création de postscript (1982)

Plusieurs formats de sortie sont disponibles (ps, pdf, png et svg). Par défaut, le fichier sera en PostScript.
Pour le changer vers le format png :

pychart.theme.output_format = "png"

Pour avoir une sortie en png, ghostscript doit être installé sur le système. PIL n’est pas nécessaire.

Invention de la télévision couleur (1938)

Par défaut, les graphiques seront uniquement en nuances de gris. Si on souhaite avoir plein de couleurs, il faut le déclarer de la manière suivante :

pychart.theme.use_color = True

Invention de l’écriture de travers (-3400)

Il est possible d’orienter le texte affiché (que ce soit pour les axes ou des boîtes de texte ad-hoc). Pour cela, il faut définir l’angle d’orientation (« /a » pour définir que l’on modifie l’angle, « -20 » pour baisser de 20 degrés par rapport à l’horizontale dans l’exemple ci-dessous) :

pychart.axis.X(label="Date", format="/a-20/hL%s")

La définition du formatage de la chaîne à afficher utilise le caractère « / » pour introduire un contrôle. Il est nécessaire de le doubler lorsque l’on souhaite l’afficher (pour une date par exemple).

Invention du dessin (Paléolithique)

pychart.line_plot.T(label=u"debian-user-french", #sert pour la légende
                    data=stats, #la structure des données à afficher
                    ycol=2, #la troisième colonne dans les données
                    line_style=_blue_line #le rendu des données
                    )

Invention du papier (IIième siècle avant JC)

La méthode draw() permet d’afficher les données sur la sortie standard. Il suffit simplement de la rediriger vers un fichier grâce au shell :

$ python volume.py  > volume.png

Code source et références

Le code qui a permis de créer le graphique :

# -*- coding: utf-8 -*-

import pychart.area
import pychart.line_plot
import pychart.theme


pychart.theme.output_format = "png"
pychart.theme.use_color = True
pychart.theme.default_font_size = 14
pychart.theme.title = "14"


stats = (("04//2012", 12.27, 14.43),
        ("05//2012", 10.71, 14.42),
        ("06//2012", 11.77, 13.10),
        ("07//2012", 10.19, 8.55),
        ("08//2012", 11.65, 14.68),
        ("09//2012", 13.00, 18.33),
        ("10//2012", 10.32, 15.06),
        ("11//2012", 6.87, 14.90),
        ("12//2012", 5.48, 10.48),
        ("01//2013", 5.29, 20.58),
        ("02//2013", 10.25, 22.39),
        ("03//2013", 9.74, 24.00),
        ("04//2013", 8.3, 19.80))


_area = pychart.area.T(size = (700, 450),
            y_grid_interval=5,
            x_coord=pychart.category_coord.T(stats, 0),
            x_axis=pychart.axis.X(label="Date", format="/a-20/hL%s"),
            y_axis=pychart.axis.Y(label="Nombre de messages par jour"),
            legend=pychart.legend.T(),
            y_range=(0, None))

_red_line = pychart.line_style.T()
_red_line.width = 2
_red_line.dash = (5, 5)
_red_line.cap_style = 2
_red_line.join_style = 2
_red_line.color = pychart.color.red
_blue_line = pychart.line_style.T()
_blue_line.width = 2
_blue_line.color = pychart.color.royalblue
_l10n_plot = pychart.line_plot.T(label=u"debian-l10n-french", data=stats, line_style=_red_line)
_user_plot = pychart.line_plot.T(label=u"debian-user-french", data=stats, ycol=2, line_style=_blue_line)
_area.add_plot(_l10n_plot, _user_plot)

_area.draw()

Les moyennes journalières ont été calculées préalablement. Elles sont exclues du code source pour ne pas l’alourdir inutilement. Voici les données brutes qui ont servi aux calculs des moyennes :
date debian-l10n-french debian-user-french nbre_jours
2012/04 368 433 30
2012/05 332 447 31
2012/06 353 393 30
2012/07 316 265 31
2012/08 361 455 31
2012/09 390 550 30
2012/10 320 467 31
2012/11 206 447 30
2012/12 170 325 31
2013/01 164 638 31
2013/02 287 627 28
2013/03 302 744 31
2013/04 190 594 30

Les données ont été récupérées à partir des versions publiques des archives des listes de traductions.

La documentation de pychart est disponible à http://home.gna.org/pychart/doc/pychart.html ou dans le paquet python-pychart-doc.

Enfin, Matplotlib répond au même besoin et semble être un projet plus vivant.

Catégories :Debian, Python Étiquettes : ,

Cohabitation Jinja et AngularJS

Jinja2, un moteur de templates en Python, et AngularJS, un framework web côté client en Javascript, utilisent tous les deux les accolades pour indiquer une variable ou une structure à interpréter.
Par exemple {{ choucroute }} pour une variable qui a du goût.

D’où conflit.
Pour résoudre ce problème, trois possibilités :

Diviser les fichiers pour régner

Séparer l’utilisation de chaque template :

  • variables Jinja uniquement dans les fichiers html fournis directement par l’application Python ;
  • variables AngularJS uniquement dans les fichiers chargés dynamiquement (par exemple /partials/detail-choucroute.html).

Simple à mettre en place mais limité.

Commenter les accolades

Protéger le code AngularJS sensible en empêchant son interprétation par Jinja :

  • {{ '{{' }} pour ne pas interpréter des accolades ;
  • {% raw %} pour un code plus long.

Changer les délimiteurs Jinja

Redéfinir les délimiteurs dans l’environnement de Jinja :

jinja_env = jinja2.Environment(...)
jinja_env.block_start_string = '(%'
jinja_env.block_end_string = '%)'
jinja_env.variable_start_string = '(('
jinja_env.variable_end_string = '))'
jinja_env.comment_start_string = '(#'
jinja_env.comment_end_string = '#)'

L’ensemble des caractères accolades ont été remplacés par des parenthèses pour une raison de cohérence. Limiter la redéfinition des délimiteurs à variable_start_string et variable_end_string devrait suffire.

Bien évidemment, libre à chacun de choisir d’autres délimiteurs.

À noter qu’il est possible d’obtenir un résultat similaire avec Django.

Catégories :Python Étiquettes : ,

Déterminer quelle est la meilleure distribution grâce à Python et PIL

12 janvier 2013 7 commentaires

N’avez-vous jamais discuté des mérites de telle ou telle distribution Linux (ou BSD) avec d’autres personnes (chacun essayant de démontrer que celle qu’il utilise au quotidien est la meilleure) ? Grâce à cet article, vous n’aurez toujours pas La réponse mais vous aurez une réponse indiscutable : il s’agit de la jouer aux cartes. Les cartes sont créées avec Python Imaging Library, une bibliothèque Python de manipulation d’image. L’article explique la façon de les réaliser.

Carte de jeu pour Debian

Chaque carte possède un ensemble de caractéristiques, la plus forte remporte le pli. Ce principe de jeu existe depuis les années 70 (Ace_Trumps, Super Top Ass). L’ensemble des 26 cartes créées est visible ici.

L’objectif de l’article est de montrer comment on peut concevoir la création de la carte, pas le détail des paramètres de chaque fonction. Pour cela, la documentation et de nombreuses explications foisonnent déjà sur le web. C’est pourquoi, par exemple, les calculs de dimensionnement ne seront pas expliqués.

Prérequis : installer PIL (ou Pillow)

Deux possibilités :

  • utiliser le système de paquet de votre distribution (python-imaging pour Debian) ;
  • utiliser Pypi : PIL n’est pas disponible, il faut installer Pillow. Cela ne change pas la façon d’utiliser la bibliothèque :
    from PIL import Image, ImageDraw, ImageFont
    

Image sert à instancier une image, ImageDraw à la modifier, ImageFont à choisir une police de caractère.

Créer une carte

La base consiste à instancier un objet image sur lequel les modifications vont être apportées. Une fois les modifications réalisées, on enregistre le résultat sur le disque :

def draw_card(distrib):
    img = Image.open(BACKGROUND_PATH)
    #ajout des textes, images, etc.
    img.save(distrib["img_name"] + ".png")

La carte est une superposition de couches, qui sont toutes fusionnées en une seule image. On a donc un fond sur lequel les autres éléments sont superposés.
Parmi les éléments remarquables :

Textes dans un cartouche

Le titre et les caractéristiques des distributions sont faits de la même manière. Il n’existe pas d’effet avec PIL pour dessiner automatiquement une bordure. Elle est réalisée en plaçant deux rectangles l’un sur l’autre, celui de dessous étant plus large et haut.

Montage du titre

def draw_title(img, title):
    """
    pour dessiner le titre avec le fond et la bordure autour

    img est l'image PIL
    title est la chaine de caracteres a afficher ("Distribution")
    """
    draw = ImageDraw.Draw(img)
    draw_cartouche(draw, 25, 55)
    font = ImageFont.truetype(FONT_PATH, TITLE_FONT_SIZE)
    width, height = draw.textsize(title, font=font)
    x = center(width)
    draw.text((x, 29), title, font=font, fill=TEXT_COLOR)

def draw_cartouche(draw, upper_height, lower_height):
    """pour dessiner les deux rectangles"""
    draw.rectangle(((MARGIN_LEFT, upper_height), (CARD_WIDTH - MARGIN_RIGHT, lower_height)),
                   fill="#8e6f32")
    BORDER = 5
    draw.rectangle(((MARGIN_LEFT + BORDER, upper_height + BORDER), (CARD_WIDTH - MARGIN_RIGHT - BORDER, lower_height - BORDER)),
                   fill="#e9b654")

draw.textsize() permet de connaître les dimensions que prendrait la chaîne passée en paramètre. Cela permet de faire un calcul (dans la fonction center(), omise dans l’extrait ci-dessus) pour centrer le texte.

Le logo de la distribution

Montage du logo

L’affichage du logo suit la même logique en intercalant une image (nommée supernova.png) entre le fond et le logo de la distribution. Pour avoir un joli rendu , on décale simplement en hauteur l’image (y) car les deux images n’ont pas la même taille.

def draw_logo(img, filename):
    """on colle supernova
       puis le fichier correspondant au parametre filename"""
    image_path = SRC_IMGS_DIR + "supernova.png"
    nova = Image.open(image_path)
    nova_width, nova_height = nova.size
    x = center(nova_width)
    y = 60
    img.paste(nova.convert("RGBA"),
              (x, y, x + nova_width, y + nova_height),
              mask=nova.convert('RGBA'))
    image_path = SRC_IMGS_DIR + filename + ".png"
    distro = Image.open(image_path)
    distro_width, distro_height = distro.size
    x = center(distro_width)
    y = 90
    img.paste(distro.convert("RGBA"),
              (x, y, x + distro_width, y + distro_height),
              mask=distro.convert('RGBA'))

L’exercice de factorisation de cette fonction est laissé aux lecteurs qui s’ennuient et qui ne sont pas partis faire autre chose (ce que je ne comprend pas d’ailleurs).

Le numéro de la carte

Le numéro de la carte en bas à gauche est affiché de biais. Si les images peuvent subir une rotation, les textes ne sont affichés qu’horizontalement. La solution est donc de coller le texte dans une image intermédiaire. Cette image subira une rotation puis sera collée sur l’image finale de la même manière que précédemment :

def draw_card_index(img, number):
    """pour dessiner 'number' de biais"""
    HEIGHT = WIDTH = 19
    num_img = Image.new("RGBA",
                        (WIDTH, HEIGHT),
                        (0, 0, 0, 0))
    num_draw = ImageDraw.Draw(num_img)
    font = ImageFont.truetype(SANS_PATH, TEXT_FONT_SIZE)
    num_draw.text((0, 0), number, font=font, fill="brown")
    n = num_img.rotate(-45)
    X, Y = 7, 378
    img.paste(n, (X, Y, X + WIDTH, Y + HEIGHT), mask=n)

À noter que la rotation faite ici prend peu de précaution et les nombres à deux chiffres sont légèrement tronqués. Le paramètre expand peut être ajouté à rotate() pour éviter la perte, mais l’image est automatiquement agrandie.

Code source et ressources

La documentation de PIL est précieuse.
Un tutoriel dont certains effets ont été réutilisés pour les cartes.

Les données sur les cartes proviennent principalement de Distrowatch. Les logos des distributions sont aussi ceux affichés sur Distrowatch. « first stable » représente la première version stable avec le nom actuel de la distribution ou ce qui pourrait être considéré comme équivalent. « based on » indique le nom de la distribution parente. La valeur « Indep. » signifie qu’elle n’est basée sur aucune autre distribution. Si elle est suivie d’une étoile, cela signifie qu’elle est maintenant indépendante mais a été basée sur une autre distribution par le passé.

Le fond de carte et l’effet supernova ont été réalisés avec The Gimp.

L’archive contenant le code source du script au cas où quelqu’un voudrait le réutiliser, ainsi que les logos et les cartes générées est fournie au format .tar.xz. Le code a été écrit dans un but de démo. Il est bien perfectible…

Catégories :Debian, Python Étiquettes : ,
Concevoir un site comme celui-ci avec WordPress.com
Commencer