Celle-ci est un peu particulière.
D’abord parce qu’elle traine dans la boîte mail depuis un siècle ou deux. Je pense que l’auteur de la demande n’en a plus besoin…
Ensuite parce que le code est assez complexe, notamment à cause de l’utilisateur d’API C. Donc si vous n’avez pas des notions de C, vous n’allez rien piger. En effet je ne vais pas expliquer les bases de C ou de Python, ce n’est pas un tuto, donc il me faut des prérequis. Je vous invite quand même à vous rafraichir la mémoire en utilisant notre introduction au module ctypes car il est massivement utilisé dans ce code.
Exceptionnellement, je ne vais pas pondre de version alternative car :
Comme d’habitude, je vais faire des remarques parfois critiques sur le code, mais mon intention n’est bien entendu pas de faire du tord à l’auteur. C’est pédagogique pour les lecteurs. D’ailleurs ce bout de code est assez impressionnant et a du demander des heures et des heures de travail tant au niveau du code que de la recherche d’information. J’insiste donc sur mon respect pour l’auteur. On est pas sur bashfr.
Je tiens tout de même à prévenir que la lib ne fonctionne pas sur ma machine, plante sur certains appels, ou produits des screenshots illisibles. Je ne doute néanmoins pas de la compétence de l’auteur, ce qu’il essaye de faire est vraiment compliqué, et ne pense que Python n’est pas son premier langage.
On m’a signalé dans le twitcoutuer qu’il manque de la zik :
Normalement le but de la lib est de permettre de faire des screenshots en pure Python sous Windows, Linux et Mac. Exemple de code sous Linux :
>>> from mss import MSSLinux
>>> mss = MSSLinux()
>>> screnshots = mss.save(output='/tmp/screenshots', oneshot=True)
>>> list(screnshots)
[u'/tmp/screenshots-full.png']
Et voici le code commenté. C’est un gros morceau, et bien complexes, avec des notions parfois que je ne maitrise pas. J’ai pu faire des erreurs et dire des bêtises. Si l’auteur passe par là, il a bien entendu un droit de réponse.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
''' A cross-platform multi-screen shot module in pure python using ctypes.
This module is maintained by Mickaël Schoentgen .
If you find problems, please submit bug reports/patches via the
GitHub issue tracker (https://github.com/BoboTiG/python-mss/issues).
Note: please keep this module compatible to Python 2.6.
Still needed:
* support for additional systems
Many thanks to all those who helped (in no particular order):
Oros, Eownis
History:
0.0.1 - first release
0.0.2 - add support for python 3 on Windows and GNU/Linux
0.0.3 - MSSImage: remove PNG filters
- MSSImage: remove 'ext' argument, using only PNG
- MSSImage: do not overwrite existing image files
- MSSImage: few optimizations into png()
- MSSLinux: few optimizations into get_pixels()
0.0.4 - MSSLinux: use of memoization => huge time/operations gains
0.0.5 - MSSWindows: few optimizations into _arrange()
- MSSImage: code simplified
You can always get the latest version of this module at:
https://raw.github.com/BoboTiG/python-mss/master/mss.py
If that URL should fail, try contacting the author.
'''
from __future__ import (unicode_literals, absolute_import,
division, print_function)
__version__ = '0.0.5'
__author__ = "Mickaël 'Tiger-222' Schoentgen"
__copyright__ = '''
Copyright (c) 2013, Mickaël 'Tiger-222' Schoentgen
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee or royalty is hereby
granted, provided that the above copyright notice appear in all copies
and that both that copyright notice and this permission notice appear
in supporting documentation or portions thereof, including
modifications, that you make.
'''
# Bon je vais pas vous expliquer ce que les lignes du dessus font hein...hein
# __all__ liste les objets importables si on fait from mss import *
# ce qui limite la pollution du namespace avec tout un tas de choses inutiles
# comme pack, isfile, system, etc. qui sont quelques lignes plus bas.
__all__ = ['MSSImage', 'MSSLinux', 'MSSMac', 'MSSWindows']
# Permet de trouve une bibliothèqe via son nom sur le système en cherchant
# divers chemins standard à l'OS
from ctypes.util import find_library
# Permet une forme de sérialisation des types simples qui est compatible
# entre Python et C
from struct import pack
# Permet de vérifier si un fichier existe et n'est pas un dossier
from os.path import isfile
# Permet de récupérer le nom de l'OS
from platform import system
# Divers opérations sur le système
import sys
# Compression zip
import zlib
# On importe conditionnellement des packages selon le système sur lequel on
# est. La raison de cela est que certains packages n'existe pas (ou ne sont pas
# utiles) sur certains OS.
# Darwin, c'est l'OS open source qui sert de base au Mac. C'est
# un mélange de NeXTSTEP et de FreeBSD.
if system() == 'Darwin':
# Quartz est la techno derrière l'affichage des Mac incluant notament
# le compositeur graphique et moteur de rendu 2D. Il a un binding Python
# intégré puisque les Mac utilisent Python un peu partout. A ce demander
# pourquoi ces neuneus nous ont collé objectifs C pour le dev.
from Quartz import *
# On importe juste l'équivalent du mimetype sous Mac pour le PNG. C'est
# une "constante"
from LaunchServices import kUTTypePNG
elif system() == 'Linux':
# Accès aux variables d'environnement
from os import environ
# Fonction pour transformer ~ dans les chemin d'accès en chemin vers
# le dossier utilisateur
from os.path import expanduser
# Parseur de xml
import xml.etree.ElementTree as ET
# 'byref' permet d'obtenir un pointer sur une fonction c,
# 'cast' est similaire à l'opérateur cast en c et permet le type casting
# d'un objet c, 'cdll' permet de charger des shared lib C
from ctypes import byref, cast, cdll
# je ne sais pas pourquoi ça n'a pas été fait une seule ligne...
# Tout ça représente les types c éponymes, mais en plus Structure est une
# classe abstraite dont on peut hériter faire une classe qui peut être
# passée en paramètre à une fonction C qui attend un struc.
from ctypes import (
c_char_p, c_int, c_int32, c_uint, c_uint32,
c_ulong, c_void_p, POINTER, Structure
)
# On hérite de Structure ce qui nous fait pour le moment une structure
# vide
class Display(Structure):
pass
# Une structure représentant les attributs d'une fenêtre pour le serveur
# d'affichage sous Linux.
class XWindowAttributes(Structure):
_fields_ = [
('x', c_int32),
('y', c_int32),
('width', c_int32),
('height', c_int32),
('border_width', c_int32),
('depth', c_int32),
('visual', c_ulong),
('root', c_ulong),
('class', c_int32),
('bit_gravity', c_int32),
('win_gravity', c_int32),
('backing_store', c_int32),
('backing_planes', c_ulong),
('backing_pixel', c_ulong),
('save_under', c_int32),
('colourmap', c_ulong),
('mapinstalled', c_uint32),
('map_state', c_uint32),
('all_event_masks', c_ulong),
('your_event_mask', c_ulong),
('do_not_propagate_mask', c_ulong),
('override_redirect', c_int32),
('screen', c_ulong)
]
# structure définissant une image telle qu'elle existe dans la mémoire
# d'un client du serveur d'affichage
class XImage(Structure):
_fields_ = [
('width' , c_int),
('height' , c_int),
('xoffset' , c_int),
('format' , c_int),
('data' , c_char_p),
('byte_order' , c_int),
('bitmap_unit' , c_int),
('bitmap_bit_order' , c_int),
('bitmap_pad' , c_int),
('depth' , c_int),
('bytes_per_line' , c_int),
('bits_per_pixel' , c_int),
('red_mask' , c_ulong),
('green_mask' , c_ulong),
('blue_mask' , c_ulong)
]
# Apparement la fonction pack avec ce format va être appelée souvent
# doc l'auteur se fait un raccourci. Le format en question est 'B', donc
# du unsigned char, et '<', donc du little endian. Et là vous comprenez
# le bonheur de travailler dans un langage de haut niveau comme Python.
def b(x):
return pack(b' '3':
display = bytes(environ['DISPLAY'], 'utf-8')
else:
display = environ['DISPLAY']
except KeyError:
err = 'MSSLinux: $DISPLAY not set. Stopping to prevent segfault.'
raise ValueError(err)
self.debug('init', '$DISPLAY', display)
# On récupère l'écran par défaut et la fenêtre racine sur l'affichage
# en cours.
# At this point, if there is no running server, it could end on
# a segmentation fault. And we cannot catch it.
self.display = self.XOpenDisplay(display)
self.debug('init', 'display', self.display)
self.screen = self.XDefaultScreen(self.display)
self.debug('init', 'screen', self.screen)
self.root = self.XDefaultRootWindow(self.display, self.screen)
self.debug('init', 'root', self.root)
# les deux méthodes qui servent à manuellement définir les types
# des autres méthodes dont j'ai parlé plus haut.
def _set_argtypes(self):
''' Functions arguments '''
self.debug('_set_argtypes')
self.XOpenDisplay.argtypes = [c_char_p]
self.XDefaultScreen.argtypes = [POINTER(Display)]
self.XDefaultRootWindow.argtypes = [POINTER(Display), c_int]
self.XGetWindowAttributes.argtypes = [POINTER(Display),
POINTER(XWindowAttributes), POINTER(XWindowAttributes)]
self.XAllPlanes.argtypes = []
self.XGetImage.argtypes = [POINTER(Display), POINTER(Display),
c_int, c_int, c_uint, c_uint, c_ulong, c_int]
self.XGetPixel.argtypes = [POINTER(XImage), c_int, c_int]
self.XFree.argtypes = [POINTER(XImage)]
self.XCloseDisplay.argtypes = [POINTER(Display)]
def _set_restypes(self):
''' Functions return type '''
self.debug('_set_restypes')
self.XOpenDisplay.restype = POINTER(Display)
self.XDefaultScreen.restype = c_int
self.XDefaultRootWindow.restype = POINTER(XWindowAttributes)
self.XGetWindowAttributes.restype = c_int
self.XAllPlanes.restype = c_ulong
self.XGetImage.restype = POINTER(XImage)
self.XGetPixel.restype = c_ulong
self.XFree.restype = c_void_p
self.XCloseDisplay.restype = c_void_p
def enum_display_monitors(self):
''' Get positions of one or more monitors.
Returns a dict with minimal requirements (see MSS class).
'''
self.debug('enum_display_monitors')
results = []
if self.oneshot:
# Dans le cas d'un seul screenshot pour tout, on récupère juste
# les coordonnées de la fenêtre racine.
gwa = XWindowAttributes()
self.XGetWindowAttributes(self.display, self.root, byref(gwa))
results.append({
b'left' : int(gwa.x),
b'top' : int(gwa.y),
b'width' : int(gwa.width),
b'height': int(gwa.height)
})
else:
# Sinon on parse le fichier XML de config pour essayer de
# trouver les coordonnées. Je ne suis pas certain que ce soit
# une bonne stratégie puisque le fichier monitors.xml contient
# tous les moniteurs jamais branché sur la machine, y compris ceux
# qu'on a pas branché depuis 1000 ans...
# It is a little more complicated, we have to guess all stuff
# from ~/.config/monitors.xml, if present.
monitors = expanduser('~/.config/monitors.xml')
if not isfile(monitors):
# Ici, lever une exception serait pas mal.
# A la place, l'auteur choisit de mettre oneshot et de relancer
# la capture. Donc au lieu d'avoir ce qu'on demande ou une
# erreur, on a ce qu'on demande ou ce qu'on ne demande pas.
# Encore une fois, j'en profite pour souligner qu'il faut
# éviter ce genre de config à base de site effects. Devoir
# setter un attribut pour avoir un résultat différent à cette
# méthode n'est pas très propre.
self.debug('ERROR', 'MSSLinux: enum_display_monitors() failed (no monitors.xml).')
self.oneshot = True
return self.enum_display_monitors()
# Le XML est une collection de noeuds 'configuration' qui représentent
# chacun un moniteur. On récupère ici le premier noeud 'configurations'
tree = ET.parse(monitors)
root = tree.getroot()
config = root.findall('configuration')[-1]
conf = []
# chaque noeud "configurations" à une série de noeuds ouput qui
# représentent chaque format de sortie (VGA, HDMI, etc). On
# boucle dessus.
for output in config.findall('output'):
name = output.get('name')
if name != 'default':
# On récupère les coordonnées, la rotation, on extrait,
# on corrige, on ajoute à la liste... Bref, même topo
# qu'avec MacOsX.
x = output.find('x')
y = output.find('y')
width = output.find('width')
height = output.find('height')
rotation = output.find('rotation')
if None not in [x, y, width, height] and name not in conf:
conf.append(name)
if rotation.text in ['left', 'right']:
width, height = height, width
results.append({
b'left' : int(x.text),
b'top' : int(y.text),
b'width' : int(width.text),
b'height' : int(height.text),
b'rotation': rotation.text
})
return results
def get_pixels(self, monitor):
''' Retreive all pixels from a monitor. Pixels have to be RGB.
'''
self.debug('get_pixels')
# On récupère les coordonnées des monitors revoyées par enum_display_monitors
width, height = monitor[b'width'], monitor[b'height']
left, top = monitor[b'left'], monitor[b'top']
ZPixmap = 2
# On récupère un masque de pixels pour l'ensemble de l'affichage
allplanes = self.XAllPlanes()
self.debug('get_pixels', 'allplanes', allplanes)
# Visiblement un fix. C'est ce qu'on appelle un commentaire utile.
# Fix for XGetImage: expected LP_Display instance instead of LP_XWindowAttributes
root = cast(self.root, POINTER(Display))
# On récupère un dump des pixels pour les coordonnées en cours, de
# l'affichage en cours, on lui applique le masque de pixel mais
# je ne sais pas pourquoi on doit le faire. x11 est une bestiole
# très tarabiscotée, et mes recherches n'ont rien donné. L'auteur
# a du bien s'amuser à trouver comment faire ce genre de chose.
image = self.XGetImage(self.display, root, left, top, width,
height, allplanes, ZPixmap)
if image is None:
raise ValueError('MSSLinux: XGetImage() failed.')
# Les pixels doivent être récupérés en RGB. L'auteur fait donc une
# fonction de conversion (c'est une fonction inline, donc jetable)
# puis l'applique à la liste des pixels qu'il récupère dans l'image.
# Une simple boucle for aurait fait l'affaire mais l'auteur a mis
# en place une stratégie de mémoisation (mise en cache) dans la fonction.
# Vu que la fonction est inline, un simple dico aurait aussi fait
# l'affaire. Mais une il est très possible qu'on lui ait donné
# le truc et comme il n'est pas habitué à Python il a juste copié/collé.
# Je le fais souvent en Java / C donc je vais pas lui jeter la pierre.
def pix(pixel, _resultats={}):
''' Apply shifts to a pixel to get the RGB values.
This method uses of memoization.
'''
# La mise en cache se fait à ce niveau.
if not pixel in _resultats:
# Là c'est du byte shifting, mais quelle logique exacte est
# implémentée, aucune idée. Il faudrait regarder les algos
# de conversion pixels vers RGB et trouver celui qui est
# appliqué. C'est le genre de truc que je copie/colle car
# je suis trop feignant et tester que ça marche est plus rapide
# que comprendre.
_resultats[pixel] = b((pixel & 16711680) >> 16) + b((pixel & 65280) >> 8) + b(pixel & 255)
return _resultats[pixel]
# Aliasing de la fonction pour gagner en vitesse en évitant un lookup
# d'attribut dans une boucle.
get_pix = self.XGetPixel
# Boucle de conversion via une liste en intention.
pixels = [pix(get_pix(image, x, y)) for y in range(height) for x in range(width)]
# Ici get_pixels retourne l'objet image plutôt que de la sauver
# dans un attribut self.image. Ouch.
self.XFree(image)
return b''.join(pixels)
# On passe maintenant à l'implémentation pour Windows
class MSSWindows(MSS):
''' Mutli-screen shot implementation for Microsoft Windows. '''
# Même topo que pour la version Linux. Même principe que son init.
def init(self):
''' Windows initialisations '''
self.debug('init')
self.GetSystemMetrics = windll.user32.GetSystemMetrics
self.EnumDisplayMonitors = windll.user32.EnumDisplayMonitors
self.GetWindowDC = windll.user32.GetWindowDC
self.CreateCompatibleDC = windll.gdi32.CreateCompatibleDC
self.CreateCompatibleBitmap = windll.gdi32.CreateCompatibleBitmap
self.SelectObject = windll.gdi32.SelectObject
self.BitBlt = windll.gdi32.BitBlt
self.GetDIBits = windll.gdi32.GetDIBits
self.DeleteObject = windll.gdi32.DeleteObject
self._set_argtypes()
self._set_restypes()
def _set_argtypes(self):
''' Functions arguments '''
self.debug('_set_argtypes')
self.MONITORENUMPROC = WINFUNCTYPE(INT, DWORD, DWORD,
POINTER(RECT), DOUBLE)
self.GetSystemMetrics.argtypes = [INT]
self.EnumDisplayMonitors.argtypes = [HDC, LPRECT,
self.MONITORENUMPROC, LPARAM]
self.GetWindowDC.argtypes = [HWND]
self.CreateCompatibleDC.argtypes = [HDC]
self.CreateCompatibleBitmap.argtypes = [HDC, INT, INT]
self.SelectObject.argtypes = [HDC, HGDIOBJ]
self.BitBlt.argtypes = [HDC, INT, INT, INT, INT, HDC, INT, INT, DWORD]
self.DeleteObject.argtypes = [HGDIOBJ]
self.GetDIBits.argtypes = [HDC, HBITMAP, UINT, UINT, LPVOID,
POINTER(BITMAPINFO), UINT]
def _set_restypes(self):
''' Functions return type '''
self.debug('_set_restypes')
self.GetSystemMetrics.restypes = INT
self.EnumDisplayMonitors.restypes = BOOL
self.GetWindowDC.restypes = HDC
self.CreateCompatibleDC.restypes = HDC
self.CreateCompatibleBitmap.restypes = HBITMAP
self.SelectObject.restypes = HGDIOBJ
self.BitBlt.restypes = BOOL
self.GetDIBits.restypes = INT
self.DeleteObject.restypes = BOOL
def enum_display_monitors(self):
''' Get positions of one or more monitors.
Returns a dict with minimal requirements (see MSS class).
'''
self.debug('enum_display_monitors')
# le code qui permet de récupérer les moniteurs est visiblement
# asynchrone, donc on fabrique un callback qui va remplir le tableau
# des résultats.
def _callback(monitor, dc, rect, data):
rct = rect.contents
results.append({
b'left' : int(rct.left),
b'top' : int(rct.top),
b'width' : int(rct.right - rct.left),
b'height': int(rct.bottom -rct.top)
})
return 1
results = []
# si c'est juste un seul screenshot, c'est un appel synchrone
if self.oneshot:
# ce sont des constantes qui déterminent quelle info ont veut que
# GetSystemMetrics renvoit. Ici on demande les coordonnées de tout
# l'écran, une par une.
SM_XVIRTUALSCREEN = 76
SM_YVIRTUALSCREEN = 77
SM_CXVIRTUALSCREEN = 78
SM_CYVIRTUALSCREEN = 79
left = self.GetSystemMetrics(SM_XVIRTUALSCREEN)
right = self.GetSystemMetrics(SM_CXVIRTUALSCREEN)
top = self.GetSystemMetrics(SM_YVIRTUALSCREEN)
bottom = self.GetSystemMetrics(SM_CYVIRTUALSCREEN)
results.append({
b'left' : int(left),
b'top' : int(top),
b'width' : int(right - left),
b'height': int(bottom - top)
})
else:
# On enrobe le callback Python dans un proxy qui le rend
# utilisable par le code C
callback = self.MONITORENUMPROC(_callback)
# On demande à windows de nous lister les moniteurs, et comme
# cet appel est asynchrone, on lui passe un callback pour qu'il
# replisse la liste au fur et à mesure
self.EnumDisplayMonitors(0, 0, callback, 0)
# On retourne la liste. A ce stade là, on ne sait pas si la liste
# est vide, à moitié remplie ou complètement remplie. Utiliser du
# code asynchrone au milieu d'une lib synchrone, c'est assez dangereux
# donc je me demande comment il retombe sur ses pieds.
return results
# A ce stade là il est 13h, j'ai commencé à 9h et j'ai la dalle. J'en ai
# marre de commenter ce code. 'envoyez-vous les codes que vous pigez pas'
# est une idée à la con. Je vous hais tous. Mon coloc va me ramener des
# frites.
def get_pixels(self, monitor):
''' Retreive all pixels from a monitor. Pixels have to be RGB. '''
self.debug('get_pixels')
# Encore une fois on récupère les coordonnées des moniteurs filées
# par enum_display_monitors
width, height = monitor[b'width'], monitor[b'height']
left, top = monitor[b'left'], monitor[b'top']
# Reajustement de la taille de la largeur. Je suppose que c'est
# empirique, mais je peux me tromper.
good_width = (width * 3 + 3) & -4
# Valeur de paramètre qui dit de copier directement dans le rectangle
# des destination.
SRCCOPY = 0xCC0020
DIB_RGB_COLORS = 0
# Récupère le Device Context (title bar, menus, and scroll bars, etc)
srcdc = self.GetWindowDC(0)
# On fabrique une copie en mémoire.
memdc = self.CreateCompatibleDC(srcdc)
# On fabrique un bitmap basé sur ce DC
bmp = self.CreateCompatibleBitmap(srcdc, width, height)
# On injecte un bitmap dans le DC en mémoire.
self.SelectObject(memdc, bmp)
# On transfert les bits d'un DC à l'autre.
self.BitBlt(memdc, 0, 0, width, height, srcdc, left, top, SRCCOPY)
# On fabrique le header du BMP
bmi = BITMAPINFO()
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER)
bmi.bmiHeader.biWidth = width
bmi.bmiHeader.biHeight = height
bmi.bmiHeader.biBitCount = 24
bmi.bmiHeader.biPlanes = 1
buffer_len = height * good_width
# On fabrique un array de char
pixels = create_string_buffer(buffer_len)
# On récupère les bits du bitmap issu du DC
bits = self.GetDIBits(memdc, bmp, 0, height, byref(pixels),
pointer(bmi), DIB_RGB_COLORS)
self.debug('get_pixels', 'srcdc', srcdc)
self.debug('get_pixels', 'memdc', memdc)
self.debug('get_pixels', 'bmp', bmp)
self.debug('get_pixels', 'buffer_len', buffer_len)
self.debug('get_pixels', 'bits', bits)
self.debug('get_pixels', 'len(pixels.raw)', len(pixels.raw))
# Nettoyage des objets.
# Clean up
self.DeleteObject(srcdc)
self.DeleteObject(memdc)
self.DeleteObject(bmp)
# Apparemment la récupération peut échouer et on peut vérifier
# cet échec en checkant la longueur. Après la cause de l'échec est
# un mystère
if bits != height or len(pixels.raw) != buffer_len:
raise ValueError('MSSWindows: GetDIBits() failed.')
# Réorganise les bits dans le bonne ordre et converti le format
# de couleur de BGR vers RGB
# Note that the origin of the returned image is in the
# bottom-left corner, 32-bit aligned. And it is BGR.
# Need to "arrange" that.
return self._arrange(pixels.raw, good_width, height)
def _arrange(self, data, width, height):
''' Reorganises data when the origin of the image is in the
bottom-left corner and converts BGR triple to RGB. '''
self.debug('_arrange')
# On crée une nouvelle liste pleine de zéro, et on la rempli
# avec la nouvelle position des pixels.
total = width * height
scanlines = [b'0'] * total
for y in range(height):
off = width * (y + 1)
offset = total - off
for x in range(0, width - 2, 3):
# On inverse aussi la position du bleu et du rouge
scanlines[off+x:off+x+3] = b(data[offset+x+2]), b(data[offset+x+1]), b(data[offset+x])
return b''.join(scanlines)
# Un wrapper pour dumper un array de pixels dans un fichier
# Typiquement un truc qui aurait pu tenir dans une fonction au lieu d'une classe.
class MSSImage(object):
''' This is a class to save data (raw pixels) to a picture file.
'''
def __init__(self, data=None, width=1, height=1):
self.data = data
self.width = int(width)
self.height = int(height)
if self.data is None:
raise ValueError('MSSImage: no data to process.')
elif self.width < 1 or self.height < 1:
raise ValueError('MSSImage: width or height must be positive.')
# Tout le boulot se passe ici.
def dump(self, output):
''' Dump data to the image file.
Pure python PNG implementation.
Image represented as RGB tuples, no interlacing.
http://inaps.org/journal/comment-fonctionne-le-png
'''
# On ouvre le fichier en mode écriture binaire.
with open(output, 'wb') as fileh:
# Pour cette partie il faut connaitre les subtilités du format PNG
# pour comprendre, ce qui n'est pas mon cas. Donc je vais faire
# de la déduction au gros doigt mouillé.
# Ca prend les données en pixel, ça en fait des morceaux de la bonne taille,
# organisés en rangés de pixels puisqu'il y a range(height) rangés.
to_take = (self.width * 3 + 3) & -4
padding = 0 if to_take % 8 == 0 else (to_take % 8) // 2
height, data = self.height, self.data
scanlines = [b''.join([b'0', data[to_take*y:to_take*y+to_take-padding]]) for y in range(height)]
# les "magic bytes", le marqueur du début de fichier qui indique
# que c'est un fichier png
magic = pack(b'>8B', 137, 80, 78, 71, 13, 10, 26, 10)
# Les metadata du fichiers : taille de l'image, une somme de
# controle, la profondeur de couleur, méthode de compression,
# le mode d'entrelacement, etc.
# Header: size, marker, data, CRC32
ihdr = [b'', b'IHDR', b'', b'']
ihdr[2] = pack(b'>2I5B', self.width, self.height, 8, 2, 0, 0, 0)
ihdr[3] = pack(b'>I', zlib.crc32(b''.join(ihdr[1:3])) & 0xffffffff)
ihdr[0] = pack(b'>I', len(ihdr[2]))
# l'image en elle même avec un marker de départ, les pixels
# et une somme de controle
# Data: size, marker, data, CRC32
idat = [b'', b'IDAT', b'', b'']
idat[2] = zlib.compress(b''.join(scanlines), 9)
idat[3] = pack(b'>I', zlib.crc32(b''.join(idat[1:3])) & 0xffffffff)
idat[0] = pack(b'>I', len(idat[2]))
# Les metadata à la fin du fichier qui sont vides.
# Footer: size, marker, None, CRC32
iend = [b'', b'IEND', b'', b'']
iend[3] = pack(b'>I', zlib.crc32(iend[1]) & 0xffffffff)
iend[0] = pack(b'>I', len(iend[2]))
# On écrit le fichier, et on retourne son nom
fileh.write(magic + b''.join(ihdr) + b''.join(idat) + b''.join(iend))
return output
return None
# Un exemple d'usage avec l'habituel if __name__ pour éviter de
# le lancer à l'import
if __name__ == '__main__':
systems = {
'Darwin' : MSSMac,
'Linux' : MSSLinux,
'Windows': MSSWindows
}
try:
MSS = systems[system()]
except KeyError:
err = 'System "{0}" not implemented.'.format(system())
raise NotImplementedError(err)
try:
mss = MSS(debug=False)
# One screen shot per monitor
for filename in mss.save():
print('File "{0}" created.'.format(filename))
# A shot to grab them all :)
for filename in mss.save(oneshot=True):
print('File "{0}" created.'.format(filename))
except Exception as ex:
print(ex)
raise
]]>