PROJET AUTOBLOG


Tiger-222

Archivé

Site original : Tiger-222

⇐ retour index

Mac & python natif : capture d'écran

mercredi 28 août 2013 à 14:40

Afin de décrire plus en détails le fonctionnement du module MSS, je vais présenter une manière de prendre une capture d'écran (ou des écrans) en utilisant seulement du python natif, à l'aide du module Quartz. Je ferai certainement des tests avec le module ctypes plus tard, mais ça ne sera pas mieux que ce qui est présenté dans cet article (plus long et fastidieux, ceci dit c'est intéressant pour le côté technique). Il existe déjà des articles qui détaillent le processus pour Windows et GNU/Linux.

Les tests ont été effectués sous Mac OS X Lion sur Virtualbox et Mac OS X Mountain Lion (merci à Eownis). D'après la documentation officielle, le script présenté ici devrait être fonctionnel sur quasiment toutes les déclinaisons de Mac OS X, voire même iOS (je m'avance peut-être, n'étant pas un fervent adepte d'Apple, je manque cruellement de matériel).


Imports


Voici la liste des imports nécessaires :
from Quartz import *

# Nécessaire pour l'enregistrement de la capture dans un fichier.
# Ici, on utilise le PNG, mais d'autres formats existent [UTType]
from LaunchServices import kUTTypePNG
[UTType] Dans le document UTType Reference, on peut trouver, entre autres, le type JPEG (kUTTypeJPEG) ou encore GIF (kUTTypeGIF).


Informations des écrans


Parce qu'il faut bien commencer quelque part, récupérons les dimensions et coordonnées des écrans disponibles. Le nom des fonctions est assez explicite :
def enum_display_monitors(oneshot=False):
# Si oneshot est à True, alors on récupère les informations de tous
# les écrans d'un coup.
# Retourne une liste de dictionnaires contenant les informations
# des écran.

results = []
if oneshot:
rect = CGRectInfinite
results.append({
b'left'  : int(rect.origin.x),
b'top'  : int(rect.origin.y),
b'width'  : int(rect.size.width),
b'height'  : int(rect.size.height)
})
else:
max_displays = 32 # Peut-être augmenté, si besoin...
rotations = {0.0: 'normal', 90.0: 'right', -90.0: 'left'}
res, ids, count = CGGetActiveDisplayList(max_displays, None, None)
for display in ids:
rect = CGRectStandardize(CGDisplayBounds(display))
left, top = rect.origin.x, rect.origin.y
width, height = rect.size.width, rect.size.height

# La fonction suivante retourne un double pour exprimer la
# rotation de l'écran : 0.0, 90.0, -90.0, ... On le traduit
# par normal, left ou right, plus compréhensible.
rot = CGDisplayRotation(display)
rotation = rotations[rot]
if rotation in ['left', 'right']:
width, height = height, width
results.append({
b'left'  : int(left),
b'top'  : int(top),
b'width'  : int(width),
b'height'  : int(height),
b'rotation': rotation
})
return results
Si onshot=True, nous utilisons CGRectInfinite qui renvoie des données assez... monstrueuses ! Je vous laisse tester pour voir ce que ça donne, ça n'est pas utilisable en l'état. Mais une option dans la fonction suivante permettra de rectifier le tir.

Exemples de retour :
# Un seul écran :
[{'width': 1280, 'top': 0, 'height': 800, 'left': 0}]

# Deux écrans :
[
{'width': 1280, 'top': 0, 'rotation': 'normal', 'height': 800, 'left': 0},
{'width': 1360, 'top': 32, 'rotation': 'normal', 'height': 768, 'left': -1360}
]

# Deux écrans, oneshot=True :
[{'width': 179769..., 'top': -89884..., 'height': 179769..., 'left': -89884...}]


Récupération des pixels et enregistrement


Le module Quartz est vraiment cool à utiliser, voyez comme il est facile de récupérer les données de l'écran et les enregistrer dans un fichier PNG :
def get_pixels_and_save(monitor, filename):
# Récupérer les pixels d'un écran, puis enregistrement.

width, height = monitor[b'width'], monitor[b'height']
left, top = monitor[b'left'], monitor[b'top']
dpi = 72

# Récupération des données brutes
rect = CGRect((left, top), (width, height))
image = CGWindowListCreateImage(
rect, kCGWindowListOptionOnScreenOnly,
kCGNullWindowID, kCGWindowImageDefault)

# Enregistrement
url = NSURL.fileURLWithPath_(filename)
dest = CGImageDestinationCreateWithURL(url, kUTTypePNG, 1, None)
properties = {
kCGImagePropertyDPIWidth: dpi,
kCGImagePropertyDPIHeight: dpi,
}
CGImageDestinationAddImage(dest, image, properties)
if CGImageDestinationFinalize(dest):
print('Fichier {0} créé.'.format(filename))
L'option kCGWindowListOptionOnScreenOnly permet de ne créer une image que de ce qui est affiché à l'écran. Elle permet de ne pas avoir une image improbable lorsqu'on récupère les dimensions via CGRectInfinite.


Boucle générale


Plus court, tu meurs :
if __name__ == '__main__':
# Une capture par écran
i = 1
for monitor in enum_display_monitors():
filename = 'mss-capture-{0}.png'.format(i)
get_pixels_and_save(monitor, filename)
i += 1

# Capture complète
monitor = enum_display_monitors(oneshot=True)[0]
filename = 'mss-capture-complet.png'
get_pixels_and_save(monitor, filename)

Voici le script complet, et la capture d'écran (merci à Eownis).


Bonus


J'ai mis en place une galerie d'images afin d'exposer les captures d'écran oneshot que vous me ferez parvenir ☺


Sources diverses