10a8c90248264a8b26970b4473770bcc3df8515fJosh Gao"""PixMapWrapper - defines the PixMapWrapper class, which wraps an opaque 20a8c90248264a8b26970b4473770bcc3df8515fJosh GaoQuickDraw PixMap data structure in a handy Python class. Also provides 30a8c90248264a8b26970b4473770bcc3df8515fJosh Gaomethods to convert to/from pixel data (from, e.g., the img module) or a 40a8c90248264a8b26970b4473770bcc3df8515fJosh GaoPython Imaging Library Image object. 50a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 60a8c90248264a8b26970b4473770bcc3df8515fJosh GaoJ. Strout <joe@strout.net> February 1999""" 70a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 80a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 90a8c90248264a8b26970b4473770bcc3df8515fJosh Gaofrom warnings import warnpy3k 100a8c90248264a8b26970b4473770bcc3df8515fJosh Gaowarnpy3k("In 3.x, the PixMapWrapper module is removed.", stacklevel=2) 110a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 120a8c90248264a8b26970b4473770bcc3df8515fJosh Gaofrom Carbon import Qd 130a8c90248264a8b26970b4473770bcc3df8515fJosh Gaofrom Carbon import QuickDraw 140a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoimport struct 150a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoimport MacOS 160a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoimport img 170a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoimport imgformat 180a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 190a8c90248264a8b26970b4473770bcc3df8515fJosh Gao# PixMap data structure element format (as used with struct) 200a8c90248264a8b26970b4473770bcc3df8515fJosh Gao_pmElemFormat = { 210a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'baseAddr':'l', # address of pixel data 220a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'rowBytes':'H', # bytes per row, plus 0x8000 230a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'bounds':'hhhh', # coordinates imposed over pixel data 240a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'top':'h', 250a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'left':'h', 260a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'bottom':'h', 270a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'right':'h', 280a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'pmVersion':'h', # flags for Color QuickDraw 290a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'packType':'h', # format of compression algorithm 300a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'packSize':'l', # size after compression 310a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'hRes':'l', # horizontal pixels per inch 320a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'vRes':'l', # vertical pixels per inch 330a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'pixelType':'h', # pixel format 340a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'pixelSize':'h', # bits per pixel 350a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'cmpCount':'h', # color components per pixel 360a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'cmpSize':'h', # bits per component 370a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'planeBytes':'l', # offset in bytes to next plane 380a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'pmTable':'l', # handle to color table 390a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'pmReserved':'l' # reserved for future use 400a8c90248264a8b26970b4473770bcc3df8515fJosh Gao} 410a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 420a8c90248264a8b26970b4473770bcc3df8515fJosh Gao# PixMap data structure element offset 430a8c90248264a8b26970b4473770bcc3df8515fJosh Gao_pmElemOffset = { 440a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'baseAddr':0, 450a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'rowBytes':4, 460a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'bounds':6, 470a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'top':6, 480a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'left':8, 490a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'bottom':10, 500a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'right':12, 510a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'pmVersion':14, 520a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'packType':16, 530a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'packSize':18, 540a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'hRes':22, 550a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'vRes':26, 560a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'pixelType':30, 570a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'pixelSize':32, 580a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'cmpCount':34, 590a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'cmpSize':36, 600a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'planeBytes':38, 610a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'pmTable':42, 620a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 'pmReserved':46 630a8c90248264a8b26970b4473770bcc3df8515fJosh Gao} 640a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 650a8c90248264a8b26970b4473770bcc3df8515fJosh Gaoclass PixMapWrapper: 660a8c90248264a8b26970b4473770bcc3df8515fJosh Gao """PixMapWrapper -- wraps the QD PixMap object in a Python class, 670a8c90248264a8b26970b4473770bcc3df8515fJosh Gao with methods to easily get/set various pixmap fields. Note: Use the 680a8c90248264a8b26970b4473770bcc3df8515fJosh Gao PixMap() method when passing to QD calls.""" 690a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 700a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def __init__(self): 710a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.__dict__['data'] = '' 720a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self._header = struct.pack("lhhhhhhhlllhhhhlll", 730a8c90248264a8b26970b4473770bcc3df8515fJosh Gao id(self.data)+MacOS.string_id_to_buffer, 740a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 0, # rowBytes 750a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 0, 0, 0, 0, # bounds 760a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 0, # pmVersion 770a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 0, 0, # packType, packSize 780a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 72<<16, 72<<16, # hRes, vRes 790a8c90248264a8b26970b4473770bcc3df8515fJosh Gao QuickDraw.RGBDirect, # pixelType 800a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 16, # pixelSize 810a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2, 5, # cmpCount, cmpSize, 820a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 0, 0, 0) # planeBytes, pmTable, pmReserved 830a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.__dict__['_pm'] = Qd.RawBitMap(self._header) 840a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 850a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def _stuff(self, element, bytes): 860a8c90248264a8b26970b4473770bcc3df8515fJosh Gao offset = _pmElemOffset[element] 870a8c90248264a8b26970b4473770bcc3df8515fJosh Gao fmt = _pmElemFormat[element] 880a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self._header = self._header[:offset] \ 890a8c90248264a8b26970b4473770bcc3df8515fJosh Gao + struct.pack(fmt, bytes) \ 900a8c90248264a8b26970b4473770bcc3df8515fJosh Gao + self._header[offset + struct.calcsize(fmt):] 910a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.__dict__['_pm'] = None 920a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 930a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def _unstuff(self, element): 940a8c90248264a8b26970b4473770bcc3df8515fJosh Gao offset = _pmElemOffset[element] 950a8c90248264a8b26970b4473770bcc3df8515fJosh Gao fmt = _pmElemFormat[element] 960a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return struct.unpack(fmt, self._header[offset:offset+struct.calcsize(fmt)])[0] 970a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 980a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def __setattr__(self, attr, val): 990a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if attr == 'baseAddr': 1000a8c90248264a8b26970b4473770bcc3df8515fJosh Gao raise 'UseErr', "don't assign to .baseAddr -- assign to .data instead" 1010a8c90248264a8b26970b4473770bcc3df8515fJosh Gao elif attr == 'data': 1020a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.__dict__['data'] = val 1030a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self._stuff('baseAddr', id(self.data) + MacOS.string_id_to_buffer) 1040a8c90248264a8b26970b4473770bcc3df8515fJosh Gao elif attr == 'rowBytes': 1050a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # high bit is always set for some odd reason 1060a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self._stuff('rowBytes', val | 0x8000) 1070a8c90248264a8b26970b4473770bcc3df8515fJosh Gao elif attr == 'bounds': 1080a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # assume val is in official Left, Top, Right, Bottom order! 1090a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self._stuff('left',val[0]) 1100a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self._stuff('top',val[1]) 1110a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self._stuff('right',val[2]) 1120a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self._stuff('bottom',val[3]) 1130a8c90248264a8b26970b4473770bcc3df8515fJosh Gao elif attr == 'hRes' or attr == 'vRes': 1140a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # 16.16 fixed format, so just shift 16 bits 1150a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self._stuff(attr, int(val) << 16) 1160a8c90248264a8b26970b4473770bcc3df8515fJosh Gao elif attr in _pmElemFormat.keys(): 1170a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # any other pm attribute -- just stuff 1180a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self._stuff(attr, val) 1190a8c90248264a8b26970b4473770bcc3df8515fJosh Gao else: 1200a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.__dict__[attr] = val 1210a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1220a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def __getattr__(self, attr): 1230a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if attr == 'rowBytes': 1240a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # high bit is always set for some odd reason 1250a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return self._unstuff('rowBytes') & 0x7FFF 1260a8c90248264a8b26970b4473770bcc3df8515fJosh Gao elif attr == 'bounds': 1270a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # return bounds in official Left, Top, Right, Bottom order! 1280a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return ( \ 1290a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self._unstuff('left'), 1300a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self._unstuff('top'), 1310a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self._unstuff('right'), 1320a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self._unstuff('bottom') ) 1330a8c90248264a8b26970b4473770bcc3df8515fJosh Gao elif attr == 'hRes' or attr == 'vRes': 1340a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # 16.16 fixed format, so just shift 16 bits 1350a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return self._unstuff(attr) >> 16 1360a8c90248264a8b26970b4473770bcc3df8515fJosh Gao elif attr in _pmElemFormat.keys(): 1370a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # any other pm attribute -- just unstuff 1380a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return self._unstuff(attr) 1390a8c90248264a8b26970b4473770bcc3df8515fJosh Gao else: 1400a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return self.__dict__[attr] 1410a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1420a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1430a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def PixMap(self): 1440a8c90248264a8b26970b4473770bcc3df8515fJosh Gao "Return a QuickDraw PixMap corresponding to this data." 1450a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if not self.__dict__['_pm']: 1460a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.__dict__['_pm'] = Qd.RawBitMap(self._header) 1470a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return self.__dict__['_pm'] 1480a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1490a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def blit(self, x1=0,y1=0,x2=None,y2=None, port=None): 1500a8c90248264a8b26970b4473770bcc3df8515fJosh Gao """Draw this pixmap into the given (default current) grafport.""" 1510a8c90248264a8b26970b4473770bcc3df8515fJosh Gao src = self.bounds 1520a8c90248264a8b26970b4473770bcc3df8515fJosh Gao dest = [x1,y1,x2,y2] 1530a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if x2 is None: 1540a8c90248264a8b26970b4473770bcc3df8515fJosh Gao dest[2] = x1 + src[2]-src[0] 1550a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if y2 is None: 1560a8c90248264a8b26970b4473770bcc3df8515fJosh Gao dest[3] = y1 + src[3]-src[1] 1570a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if not port: port = Qd.GetPort() 1580a8c90248264a8b26970b4473770bcc3df8515fJosh Gao Qd.CopyBits(self.PixMap(), port.GetPortBitMapForCopyBits(), src, tuple(dest), 1590a8c90248264a8b26970b4473770bcc3df8515fJosh Gao QuickDraw.srcCopy, None) 1600a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1610a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def fromstring(self,s,width,height,format=imgformat.macrgb): 1620a8c90248264a8b26970b4473770bcc3df8515fJosh Gao """Stuff this pixmap with raw pixel data from a string. 1630a8c90248264a8b26970b4473770bcc3df8515fJosh Gao Supply width, height, and one of the imgformat specifiers.""" 1640a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # we only support 16- and 32-bit mac rgb... 1650a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # so convert if necessary 1660a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if format != imgformat.macrgb and format != imgformat.macrgb16: 1670a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # (LATER!) 1680a8c90248264a8b26970b4473770bcc3df8515fJosh Gao raise "NotImplementedError", "conversion to macrgb or macrgb16" 1690a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.data = s 1700a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.bounds = (0,0,width,height) 1710a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.cmpCount = 3 1720a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.pixelType = QuickDraw.RGBDirect 1730a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if format == imgformat.macrgb: 1740a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.pixelSize = 32 1750a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.cmpSize = 8 1760a8c90248264a8b26970b4473770bcc3df8515fJosh Gao else: 1770a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.pixelSize = 16 1780a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.cmpSize = 5 1790a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.rowBytes = width*self.pixelSize/8 1800a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1810a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def tostring(self, format=imgformat.macrgb): 1820a8c90248264a8b26970b4473770bcc3df8515fJosh Gao """Return raw data as a string in the specified format.""" 1830a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # is the native format requested? if so, just return data 1840a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if (format == imgformat.macrgb and self.pixelSize == 32) or \ 1850a8c90248264a8b26970b4473770bcc3df8515fJosh Gao (format == imgformat.macrgb16 and self.pixelsize == 16): 1860a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return self.data 1870a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # otherwise, convert to the requested format 1880a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # (LATER!) 1890a8c90248264a8b26970b4473770bcc3df8515fJosh Gao raise "NotImplementedError", "data format conversion" 1900a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 1910a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def fromImage(self,im): 1920a8c90248264a8b26970b4473770bcc3df8515fJosh Gao """Initialize this PixMap from a PIL Image object.""" 1930a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # We need data in ARGB format; PIL can't currently do that, 1940a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # but it can do RGBA, which we can use by inserting one null 1950a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # up frontpm = 1960a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if im.mode != 'RGBA': im = im.convert('RGBA') 1970a8c90248264a8b26970b4473770bcc3df8515fJosh Gao data = chr(0) + im.tostring() 1980a8c90248264a8b26970b4473770bcc3df8515fJosh Gao self.fromstring(data, im.size[0], im.size[1]) 1990a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2000a8c90248264a8b26970b4473770bcc3df8515fJosh Gao def toImage(self): 2010a8c90248264a8b26970b4473770bcc3df8515fJosh Gao """Return the contents of this PixMap as a PIL Image object.""" 2020a8c90248264a8b26970b4473770bcc3df8515fJosh Gao import Image 2030a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # our tostring() method returns data in ARGB format, 2040a8c90248264a8b26970b4473770bcc3df8515fJosh Gao # whereas Image uses RGBA; a bit of slicing fixes this... 2050a8c90248264a8b26970b4473770bcc3df8515fJosh Gao data = self.tostring()[1:] + chr(0) 2060a8c90248264a8b26970b4473770bcc3df8515fJosh Gao bounds = self.bounds 2070a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return Image.fromstring('RGBA',(bounds[2]-bounds[0],bounds[3]-bounds[1]),data) 2080a8c90248264a8b26970b4473770bcc3df8515fJosh Gao 2090a8c90248264a8b26970b4473770bcc3df8515fJosh Gaodef test(): 2100a8c90248264a8b26970b4473770bcc3df8515fJosh Gao import MacOS 2110a8c90248264a8b26970b4473770bcc3df8515fJosh Gao import EasyDialogs 2120a8c90248264a8b26970b4473770bcc3df8515fJosh Gao import Image 2130a8c90248264a8b26970b4473770bcc3df8515fJosh Gao path = EasyDialogs.AskFileForOpen("Image File:") 2140a8c90248264a8b26970b4473770bcc3df8515fJosh Gao if not path: return 2150a8c90248264a8b26970b4473770bcc3df8515fJosh Gao pm = PixMapWrapper() 2160a8c90248264a8b26970b4473770bcc3df8515fJosh Gao pm.fromImage( Image.open(path) ) 2170a8c90248264a8b26970b4473770bcc3df8515fJosh Gao pm.blit(20,20) 2180a8c90248264a8b26970b4473770bcc3df8515fJosh Gao return pm 219