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