Home | History | Annotate | Download | only in plat-mac
      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 (at] strout.net>  February 1999"""
      7 
      8 
      9 from warnings import warnpy3k
     10 warnpy3k("In 3.x, the PixMapWrapper module is removed.", stacklevel=2)
     11 
     12 from Carbon import Qd
     13 from Carbon import QuickDraw
     14 import struct
     15 import MacOS
     16 import img
     17 import 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 
     65 class 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 
    209 def 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