| """PixMapWrapper - defines the PixMapWrapper class, which wraps an opaque |
| QuickDraw PixMap data structure in a handy Python class. Also provides |
| methods to convert to/from pixel data (from, e.g., the img module) or a |
| Python Imaging Library Image object. |
| |
| J. Strout <joe@strout.net> February 1999""" |
| |
| import Qd |
| import QuickDraw |
| import struct |
| import MacOS |
| import img |
| import imgformat |
| |
| # PixMap data structure element format (as used with struct) |
| _pmElemFormat = { |
| 'baseAddr':'l', # address of pixel data |
| 'rowBytes':'h', # bytes per row, plus 0x8000 |
| 'bounds':'hhhh', # coordinates imposed over pixel data |
| 'top':'h', |
| 'left':'h', |
| 'bottom':'h', |
| 'right':'h', |
| 'pmVersion':'h', # flags for Color QuickDraw |
| 'packType':'h', # format of compression algorithm |
| 'packSize':'l', # size after compression |
| 'hRes':'l', # horizontal pixels per inch |
| 'vRes':'l', # vertical pixels per inch |
| 'pixelType':'h', # pixel format |
| 'pixelSize':'h', # bits per pixel |
| 'cmpCount':'h', # color components per pixel |
| 'cmpSize':'h', # bits per component |
| 'planeBytes':'l', # offset in bytes to next plane |
| 'pmTable':'l', # handle to color table |
| 'pmReserved':'l' # reserved for future use |
| } |
| |
| # PixMap data structure element offset |
| _pmElemOffset = { |
| 'baseAddr':0, |
| 'rowBytes':4, |
| 'bounds':6, |
| 'top':6, |
| 'left':8, |
| 'bottom':10, |
| 'right':12, |
| 'pmVersion':14, |
| 'packType':16, |
| 'packSize':18, |
| 'hRes':22, |
| 'vRes':26, |
| 'pixelType':30, |
| 'pixelSize':32, |
| 'cmpCount':34, |
| 'cmpSize':36, |
| 'planeBytes':38, |
| 'pmTable':42, |
| 'pmReserved':46 |
| } |
| |
| class PixMapWrapper: |
| """PixMapWrapper -- wraps the QD PixMap object in a Python class, |
| with methods to easily get/set various pixmap fields. Note: Use the |
| PixMap() method when passing to QD calls.""" |
| |
| def __init__(self): |
| self.__dict__['data'] = '' |
| self._header = struct.pack("lhhhhhhhlllhhhhlll", |
| id(self.data)+MacOS.string_id_to_buffer, |
| 0, # rowBytes |
| 0, 0, 0, 0, # bounds |
| 0, # pmVersion |
| 0, 0, # packType, packSize |
| 72<<16, 72<<16, # hRes, vRes |
| QuickDraw.RGBDirect, # pixelType |
| 16, # pixelSize |
| 2, 5, # cmpCount, cmpSize, |
| 0, 0, 0) # planeBytes, pmTable, pmReserved |
| self.__dict__['_pm'] = Qd.RawBitMap(self._header) |
| |
| def _stuff(self, element, bytes): |
| offset = _pmElemOffset[element] |
| fmt = _pmElemFormat[element] |
| self._header = self._header[:offset] \ |
| + struct.pack(fmt, bytes) \ |
| + self._header[offset + struct.calcsize(fmt):] |
| self.__dict__['_pm'] = None |
| |
| def _unstuff(self, element): |
| offset = _pmElemOffset[element] |
| fmt = _pmElemFormat[element] |
| return struct.unpack(fmt, self._header[offset:offset+struct.calcsize(fmt)])[0] |
| |
| def __setattr__(self, attr, val): |
| if attr == 'baseAddr': |
| raise 'UseErr', "don't assign to .baseAddr -- assign to .data instead" |
| elif attr == 'data': |
| self.__dict__['data'] = val |
| self._stuff('baseAddr', id(self.data) + MacOS.string_id_to_buffer) |
| elif attr == 'rowBytes': |
| # high bit is always set for some odd reason |
| self._stuff('rowBytes', val | 0x8000) |
| elif attr == 'bounds': |
| # assume val is in official Left, Top, Right, Bottom order! |
| self._stuff('left',val[0]) |
| self._stuff('top',val[1]) |
| self._stuff('right',val[2]) |
| self._stuff('bottom',val[3]) |
| elif attr == 'hRes' or attr == 'vRes': |
| # 16.16 fixed format, so just shift 16 bits |
| self._stuff(attr, int(val) << 16) |
| elif attr in _pmElemFormat.keys(): |
| # any other pm attribute -- just stuff |
| self._stuff(attr, val) |
| else: |
| self.__dict__[attr] = val |
| |
| def __getattr__(self, attr): |
| if attr == 'rowBytes': |
| # high bit is always set for some odd reason |
| return self._unstuff('rowBytes') & 0x7FFF |
| elif attr == 'bounds': |
| # return bounds in official Left, Top, Right, Bottom order! |
| return ( \ |
| self._unstuff('left'), |
| self._unstuff('top'), |
| self._unstuff('right'), |
| self._unstuff('bottom') ) |
| elif attr == 'hRes' or attr == 'vRes': |
| # 16.16 fixed format, so just shift 16 bits |
| return self._unstuff(attr) >> 16 |
| elif attr in _pmElemFormat.keys(): |
| # any other pm attribute -- just unstuff |
| return self._unstuff(attr) |
| else: |
| return self.__dict__[attr] |
| |
| |
| def PixMap(self): |
| "Return a QuickDraw PixMap corresponding to this data." |
| if not self.__dict__['_pm']: |
| self.__dict__['_pm'] = Qd.RawBitMap(self._header) |
| return self.__dict__['_pm'] |
| |
| def blit(self, x1=0,y1=0,x2=None,y2=None, port=None): |
| """Draw this pixmap into the given (default current) grafport.""" |
| src = self.bounds |
| dest = [x1,y1,x2,y2] |
| if x2 == None: |
| dest[2] = x1 + src[2]-src[0] |
| if y2 == None: |
| dest[3] = y1 + src[3]-src[1] |
| if not port: port = Qd.GetPort() |
| Qd.CopyBits(self.PixMap(), port.portBits, src, tuple(dest), |
| QuickDraw.srcCopy, None) |
| |
| def fromstring(self,s,width,height,format=imgformat.macrgb): |
| """Stuff this pixmap with raw pixel data from a string. |
| Supply width, height, and one of the imgformat specifiers.""" |
| # we only support 16- and 32-bit mac rgb... |
| # so convert if necessary |
| if format != imgformat.macrgb and format != imgformat.macrgb16: |
| # (LATER!) |
| raise "NotImplementedError", "conversion to macrgb or macrgb16" |
| self.data = s |
| self.bounds = (0,0,width,height) |
| self.cmpCount = 3 |
| self.pixelType = QuickDraw.RGBDirect |
| if format == imgformat.macrgb: |
| self.pixelSize = 32 |
| self.cmpSize = 8 |
| else: |
| self.pixelSize = 16 |
| self.cmpSize = 5 |
| self.rowBytes = width*self.pixelSize/8 |
| |
| def tostring(self, format=imgformat.macrgb): |
| """Return raw data as a string in the specified format.""" |
| # is the native format requested? if so, just return data |
| if (format == imgformat.macrgb and self.pixelSize == 32) or \ |
| (format == imgformat.macrgb16 and self.pixelsize == 16): |
| return self.data |
| # otherwise, convert to the requested format |
| # (LATER!) |
| raise "NotImplementedError", "data format conversion" |
| |
| def fromImage(self,im): |
| """Initialize this PixMap from a PIL Image object.""" |
| # We need data in ARGB format; PIL can't currently do that, |
| # but it can do RGBA, which we can use by inserting one null |
| # up frontpm = |
| if im.mode != 'RGBA': im = im.convert('RGBA') |
| data = chr(0) + im.tostring() |
| self.fromstring(data, im.size[0], im.size[1]) |
| |
| def toImage(self): |
| """Return the contents of this PixMap as a PIL Image object.""" |
| import Image |
| # our tostring() method returns data in ARGB format, |
| # whereas Image uses RGBA; a bit of slicing fixes this... |
| data = self.tostring()[1:] + chr(0) |
| bounds = self.bounds |
| return Image.fromstring('RGBA',(bounds[2]-bounds[0],bounds[3]-bounds[1]),data) |
| |
| def test(): |
| import MacOS |
| import macfs |
| import Image |
| fsspec, ok = macfs.PromptGetFile("Image File:") |
| if not ok: return |
| path = fsspec.as_pathname() |
| pm = PixMapWrapper() |
| pm.fromImage( Image.open(path) ) |
| pm.blit(20,20) |
| return pm |
| |