1"""PixMapWrapper - defines the PixMapWrapper class, which wraps an opaque 2QuickDraw PixMap data structure in a handy Python class. Also provides 3methods to convert to/from pixel data (from, e.g., the img module) or a 4Python Imaging Library Image object. 5 6J. Strout <joe@strout.net> February 1999""" 7 8 9from warnings import warnpy3k 10warnpy3k("In 3.x, the PixMapWrapper module is removed.", stacklevel=2) 11 12from Carbon import Qd 13from Carbon import QuickDraw 14import struct 15import MacOS 16import img 17import imgformat 18 19# PixMap data structure element format (as used with struct) 20_pmElemFormat = { 21 'baseAddr':'l', # address of pixel data 22 'rowBytes':'H', # bytes per row, plus 0x8000 23 'bounds':'hhhh', # coordinates imposed over pixel data 24 'top':'h', 25 'left':'h', 26 'bottom':'h', 27 'right':'h', 28 'pmVersion':'h', # flags for Color QuickDraw 29 'packType':'h', # format of compression algorithm 30 'packSize':'l', # size after compression 31 'hRes':'l', # horizontal pixels per inch 32 'vRes':'l', # vertical pixels per inch 33 'pixelType':'h', # pixel format 34 'pixelSize':'h', # bits per pixel 35 'cmpCount':'h', # color components per pixel 36 'cmpSize':'h', # bits per component 37 'planeBytes':'l', # offset in bytes to next plane 38 'pmTable':'l', # handle to color table 39 'pmReserved':'l' # reserved for future use 40} 41 42# PixMap data structure element offset 43_pmElemOffset = { 44 'baseAddr':0, 45 'rowBytes':4, 46 'bounds':6, 47 'top':6, 48 'left':8, 49 'bottom':10, 50 'right':12, 51 'pmVersion':14, 52 'packType':16, 53 'packSize':18, 54 'hRes':22, 55 'vRes':26, 56 'pixelType':30, 57 'pixelSize':32, 58 'cmpCount':34, 59 'cmpSize':36, 60 'planeBytes':38, 61 'pmTable':42, 62 'pmReserved':46 63} 64 65class PixMapWrapper: 66 """PixMapWrapper -- wraps the QD PixMap object in a Python class, 67 with methods to easily get/set various pixmap fields. Note: Use the 68 PixMap() method when passing to QD calls.""" 69 70 def __init__(self): 71 self.__dict__['data'] = '' 72 self._header = struct.pack("lhhhhhhhlllhhhhlll", 73 id(self.data)+MacOS.string_id_to_buffer, 74 0, # rowBytes 75 0, 0, 0, 0, # bounds 76 0, # pmVersion 77 0, 0, # packType, packSize 78 72<<16, 72<<16, # hRes, vRes 79 QuickDraw.RGBDirect, # pixelType 80 16, # pixelSize 81 2, 5, # cmpCount, cmpSize, 82 0, 0, 0) # planeBytes, pmTable, pmReserved 83 self.__dict__['_pm'] = Qd.RawBitMap(self._header) 84 85 def _stuff(self, element, bytes): 86 offset = _pmElemOffset[element] 87 fmt = _pmElemFormat[element] 88 self._header = self._header[:offset] \ 89 + struct.pack(fmt, bytes) \ 90 + self._header[offset + struct.calcsize(fmt):] 91 self.__dict__['_pm'] = None 92 93 def _unstuff(self, element): 94 offset = _pmElemOffset[element] 95 fmt = _pmElemFormat[element] 96 return struct.unpack(fmt, self._header[offset:offset+struct.calcsize(fmt)])[0] 97 98 def __setattr__(self, attr, val): 99 if attr == 'baseAddr': 100 raise 'UseErr', "don't assign to .baseAddr -- assign to .data instead" 101 elif attr == 'data': 102 self.__dict__['data'] = val 103 self._stuff('baseAddr', id(self.data) + MacOS.string_id_to_buffer) 104 elif attr == 'rowBytes': 105 # high bit is always set for some odd reason 106 self._stuff('rowBytes', val | 0x8000) 107 elif attr == 'bounds': 108 # assume val is in official Left, Top, Right, Bottom order! 109 self._stuff('left',val[0]) 110 self._stuff('top',val[1]) 111 self._stuff('right',val[2]) 112 self._stuff('bottom',val[3]) 113 elif attr == 'hRes' or attr == 'vRes': 114 # 16.16 fixed format, so just shift 16 bits 115 self._stuff(attr, int(val) << 16) 116 elif attr in _pmElemFormat.keys(): 117 # any other pm attribute -- just stuff 118 self._stuff(attr, val) 119 else: 120 self.__dict__[attr] = val 121 122 def __getattr__(self, attr): 123 if attr == 'rowBytes': 124 # high bit is always set for some odd reason 125 return self._unstuff('rowBytes') & 0x7FFF 126 elif attr == 'bounds': 127 # return bounds in official Left, Top, Right, Bottom order! 128 return ( \ 129 self._unstuff('left'), 130 self._unstuff('top'), 131 self._unstuff('right'), 132 self._unstuff('bottom') ) 133 elif attr == 'hRes' or attr == 'vRes': 134 # 16.16 fixed format, so just shift 16 bits 135 return self._unstuff(attr) >> 16 136 elif attr in _pmElemFormat.keys(): 137 # any other pm attribute -- just unstuff 138 return self._unstuff(attr) 139 else: 140 return self.__dict__[attr] 141 142 143 def PixMap(self): 144 "Return a QuickDraw PixMap corresponding to this data." 145 if not self.__dict__['_pm']: 146 self.__dict__['_pm'] = Qd.RawBitMap(self._header) 147 return self.__dict__['_pm'] 148 149 def blit(self, x1=0,y1=0,x2=None,y2=None, port=None): 150 """Draw this pixmap into the given (default current) grafport.""" 151 src = self.bounds 152 dest = [x1,y1,x2,y2] 153 if x2 is None: 154 dest[2] = x1 + src[2]-src[0] 155 if y2 is None: 156 dest[3] = y1 + src[3]-src[1] 157 if not port: port = Qd.GetPort() 158 Qd.CopyBits(self.PixMap(), port.GetPortBitMapForCopyBits(), src, tuple(dest), 159 QuickDraw.srcCopy, None) 160 161 def fromstring(self,s,width,height,format=imgformat.macrgb): 162 """Stuff this pixmap with raw pixel data from a string. 163 Supply width, height, and one of the imgformat specifiers.""" 164 # we only support 16- and 32-bit mac rgb... 165 # so convert if necessary 166 if format != imgformat.macrgb and format != imgformat.macrgb16: 167 # (LATER!) 168 raise "NotImplementedError", "conversion to macrgb or macrgb16" 169 self.data = s 170 self.bounds = (0,0,width,height) 171 self.cmpCount = 3 172 self.pixelType = QuickDraw.RGBDirect 173 if format == imgformat.macrgb: 174 self.pixelSize = 32 175 self.cmpSize = 8 176 else: 177 self.pixelSize = 16 178 self.cmpSize = 5 179 self.rowBytes = width*self.pixelSize/8 180 181 def tostring(self, format=imgformat.macrgb): 182 """Return raw data as a string in the specified format.""" 183 # is the native format requested? if so, just return data 184 if (format == imgformat.macrgb and self.pixelSize == 32) or \ 185 (format == imgformat.macrgb16 and self.pixelsize == 16): 186 return self.data 187 # otherwise, convert to the requested format 188 # (LATER!) 189 raise "NotImplementedError", "data format conversion" 190 191 def fromImage(self,im): 192 """Initialize this PixMap from a PIL Image object.""" 193 # We need data in ARGB format; PIL can't currently do that, 194 # but it can do RGBA, which we can use by inserting one null 195 # up frontpm = 196 if im.mode != 'RGBA': im = im.convert('RGBA') 197 data = chr(0) + im.tostring() 198 self.fromstring(data, im.size[0], im.size[1]) 199 200 def toImage(self): 201 """Return the contents of this PixMap as a PIL Image object.""" 202 import Image 203 # our tostring() method returns data in ARGB format, 204 # whereas Image uses RGBA; a bit of slicing fixes this... 205 data = self.tostring()[1:] + chr(0) 206 bounds = self.bounds 207 return Image.fromstring('RGBA',(bounds[2]-bounds[0],bounds[3]-bounds[1]),data) 208 209def test(): 210 import MacOS 211 import EasyDialogs 212 import Image 213 path = EasyDialogs.AskFileForOpen("Image File:") 214 if not path: return 215 pm = PixMapWrapper() 216 pm.fromImage( Image.open(path) ) 217 pm.blit(20,20) 218 return pm 219