|
| 1 | +"""PixMapWrapper - defines the PixMapWrapper class, which wraps an opaque |
| 2 | +QuickDraw PixMap data structure in a handy Python class. Also provides |
| 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 | + |
| 8 | +import Qd |
| 9 | +import QuickDraw |
| 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 = { |
| 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 |
| 36 | +} |
| 37 | + |
| 38 | +# PixMap data structure element offset |
| 39 | +_pmElemOffset = { |
| 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 |
| 59 | +} |
| 60 | + |
| 61 | +class PixMapWrapper: |
| 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.""" |
| 65 | + |
| 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) |
| 80 | + |
| 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 |
| 88 | + |
| 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] |
| 93 | + |
| 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: |
| 116 | + self.__dict__[attr] = val |
| 117 | + |
| 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: |
| 136 | + return self.__dict__[attr] |
| 137 | + |
| 138 | + |
| 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'] |
| 144 | + |
| 145 | + def blit(self, x1=0,y1=0,x2=None,y2=None, port=None): |
| 146 | + """Draw this pixmap into the given (default current) grafport.""" |
| 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.portBits, src, tuple(dest), |
| 155 | + QuickDraw.srcCopy, None) |
| 156 | + |
| 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 |
| 176 | + |
| 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" |
| 186 | + |
| 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 |
| 191 | + # up frontpm = |
| 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]) |
| 195 | + |
| 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) |
| 204 | + |
| 205 | +def test(): |
| 206 | + import MacOS |
| 207 | + import macfs |
| 208 | + import Image |
| 209 | + fsspec, ok = macfs.PromptGetFile("Image File:") |
| 210 | + if not ok: return |
| 211 | + path = fsspec.as_pathname() |
| 212 | + pm = PixMapWrapper() |
| 213 | + pm.fromImage( Image.open(path) ) |
| 214 | + pm.blit(20,20) |
| 215 | + return pm |
| 216 | + |
0 commit comments