Saturday, April 20, 2013

Python Script for getting pixel color on screen in OSX

This post on stackoverflow: http://stackoverflow.com/questions/12978846/python-get-screen-pixel-value-in-os-x, and this blog post: http://neverfear.org/blog/view/156/OS_X_Screen_capture_from_Python_PyObjC describe a pixel-color-grabbing method using python on OSX. Here is a simplified version, which takes command line arguments and is made to only capture a 1x1 pixel region (this makes it faster for just this functionality) (btw, I shall do my best to get this to format correctly for copying and pasting):

#!/usr/bin/python
import struct
import operator
import Quartz.CoreGraphics as CG
import sys, getopt

def main():
    args = getopt.getopt(sys.argv[1:], '')
    twoArgs = len(args[1]) == 2
    
    #print args[1]
    #error correction here to check for [correct] arguments
    
    if twoArgs:
        argx = args[1][0]
        argy = args[1][1]
        try:
            global x
            x = int(argx)
            global y
            y = int(argy)
        except ValueError:
            print "Error! Enter two integers as arguments."
            #print x
            #print y
        else:
            sp = ScreenPixel()
            sp.capture()
            print sp.pixel(0, 0)
    else:
        print "Error! Script requires two integers as arguments."

class ScreenPixel(object):
    """Captures the screen using CoreGraphics, and provides access to
    the pixel values.
    [crg:]
    ... pass two arguments to script (no comma, no nuthin', a la:
    ./thisScript.py 112 767
    """
 
    def capture(self):
        """see original version for capturing full screen
        (and lots of other stuff)
        """
 
        region = CG.CGRectMake(x, y, 1, 1)
 
        # Create screenshot as CGImage
        image = CG.CGWindowListCreateImage(
            region,
            CG.kCGWindowListOptionOnScreenOnly,
            CG.kCGNullWindowID,
            CG.kCGWindowImageDefault)
 
        # Intermediate step, get pixel data as CGDataProvider
        prov = CG.CGImageGetDataProvider(image)
 
        # Copy data out of CGDataProvider, becomes string of bytes
        self._data = CG.CGDataProviderCopyData(prov)
 
        # Get width/height of image
        self.width = CG.CGImageGetWidth(image)
        self.height = CG.CGImageGetHeight(image)
 
    def pixel(self, x, y):
        """Get pixel value at given (x,y) screen coordinates
 
        Must call capture first.
        """

        # Pixel data is unsigned char (8bit unsigned integer),
        # and there are four (blue,green,red,alpha)
        data_format = "BBBB"
 
        # Calculate offset, based on
        # http://www.markj.net/iphone-uiimage-pixel-color/
        
        # [crg]: removed this -- unnecessary step, just using zero
        #offset = 4 * ((self.width*int(round(y))) + int(round(x)))
 
        # Unpack data from string into Python'y integers
        b, g, r, a = struct.unpack_from(data_format, self._data, offset=0)
 
        # Return BGRA as RGBA
        return (r, g, b)
        #can (used to) return alpha, too, but in this context, unnecessary
 
 
if __name__ == '__main__':
    main()