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