Package rdkit :: Package Chem :: Package Draw :: Module cairoCanvas

[hide private]
[frames] | no frames]

# Source Code for Module rdkit.Chem.Draw.cairoCanvas

      1  # $Id: cairoCanvas.py 11930 2014-01-24 07:00:08Z landrgr1 $
      2  #
      3  #  Copyright (C) 2008 Greg Landrum
      4  #  Copyright (C) 2009 Uwe Hoffmann
      5  #
      6  #   @@ All Rights Reserved @@
      7  #  This file is part of the RDKit.
      8  #  The contents are covered by the terms of the BSD license
      9  #  which is included in the file license.txt, found at the root
     10  #  of the RDKit source tree.
     11  #
     12  #pylint: disable=F0401,C0324,C0322,W0142
     13  import sys
     14
     15  from rdkit import six
     16  if not six.PY3:
     17    bytes = buffer
     18
     19  # for Python3, import cairocffi preferably
     20  if six.PY3:
     21    try:
     22      import cairocffi as cairo
     23    except ImportError:
     24      import cairo
     25  else:
     26    try:
     27      import cairo
     28    except ImportError:
     29      import cairocffi as cairo
     30
     31  if not hasattr(cairo.ImageSurface,'get_data') and \
     32     not hasattr(cairo.ImageSurface,'get_data_as_rgba'):
     33    raise ImportError('cairo version too old')
     34
     35  import math
     36  import rdkit.RDConfig
     37  import os,re
     38  import array
     39  if not 'RDK_NOPANGO' in os.environ:
     40    try:
     41      import pangocairo
     42    except ImportError:
     43      pangocairo=None
     44    try:
     45      import pango
     46    except ImportError:
     47      pango=None
     48  else:
     49    pango=None
     50    pangocairo=None
     51
     52  from rdkit.Chem.Draw.canvasbase import CanvasBase
     53  from PIL import Image
     54
     55  scriptPattern=re.compile(r'\<.+?\>')
     56
     57 -class Canvas(CanvasBase):
     58 -  def __init__(self,
     59                 image=None,  # PIL image
     60                 size=None,
     61                 ctx=None,
     62                 imageType=None, # determines file type
     63                 fileName=None,  # if set determines output file name
     64                 ):
     65      """
     66      Canvas can be used in four modes:
     67      1) using the supplied PIL image
     68      2) using the supplied cairo context ctx
     69      3) writing to a file fileName with image type imageType
     70      4) creating a cairo surface and context within the constructor
     71      """
     72      self.image=None
     73      self.imageType=imageType
     74      if image is not None:
     75        try:
     76          imgd = getattr(image,'tobytes',image.tostring)("raw","BGRA")
     77        except SystemError:
     78          r,g,b,a = image.split()
     79          mrg = Image.merge("RGBA",(b,g,r,a))
     80          imgd = getattr(mrg,'tobytes',mrg.tostring)("raw","RGBA")
     81
     82        a = array.array('B',imgd)
     83        stride=image.size[0]*4
     84        surface = cairo.ImageSurface.create_for_data (
     85          a, cairo.FORMAT_ARGB32,
     86          image.size[0], image.size[1], stride)
     87        ctx = cairo.Context(surface)
     88        size=image.size[0], image.size[1]
     89        self.image=image
     90      elif ctx is None and size is not None:
     91        if hasattr(cairo, "PDFSurface") and imageType == "pdf":
     92          surface = cairo.PDFSurface(fileName, size[0], size[1])
     93        elif hasattr(cairo, "SVGSurface") and imageType == "svg":
     94          surface = cairo.SVGSurface(fileName, size[0], size[1])
     95        elif hasattr(cairo, "PSSurface") and imageType == "ps":
     96          surface = cairo.PSSurface(fileName, size[0], size[1])
     97        elif imageType == "png":
     98          surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, size[0], size[1])
     99        else:
    100          raise ValueError("Unrecognized file type. Valid choices are pdf, svg, ps, and png")
    101        ctx = cairo.Context(surface)
    102        ctx.set_source_rgb(1,1,1)
    103        ctx.paint()
    104      else:
    105        surface = ctx.get_target()
    106        if size is None:
    107          try:
    108            size = surface.get_width(),surface.get_height()
    109          except AttributeError:
    110            size = None
    111      self.ctx = ctx
    112      self.size = size
    113      self.surface = surface
    114      self.fileName = fileName
    115
    116 -  def flush(self):
    117      """temporary interface, must be splitted to different methods,
    118      """
    119      if self.fileName and self.imageType=='png':
    120        self.surface.write_to_png(self.fileName)
    121      elif self.image is not None:
    122        # on linux at least it seems like the PIL images are BGRA, not RGBA:
    123        if hasattr(self.surface,'get_data'):
    124          getattr(self.image,'frombytes',self.image.fromstring)(bytes(self.surface.get_data()),
    125                                                                "raw","BGRA",0,1)
    126        else:
    127          getattr(self.image,'frombytes',self.image.fromstring)(bytes(self.surface.get_data_as_rgba()),
    128                                                                "raw","RGBA",0,1)
    129        self.surface.finish()
    130      elif self.imageType == "png":
    131        if hasattr(self.surface,'get_data'):
    132          buffer=self.surface.get_data()
    133        else:
    134          buffer=self.surface.get_data_as_rgba()
    135        return buffer
    136
    137 -  def _doLine(self, p1, p2, **kwargs):
    138      if kwargs.get('dash',(0,0)) == (0,0):
    139        self.ctx.move_to(p1[0],p1[1])
    140        self.ctx.line_to(p2[0],p2[1])
    141      else:
    142        dash = kwargs['dash']
    143        pts = self._getLinePoints(p1,p2,dash)
    144
    145        currDash = 0
    146        dashOn = True
    147        while currDash<(len(pts)-1):
    148          if dashOn:
    149            p1 = pts[currDash]
    150            p2 = pts[currDash+1]
    151            self.ctx.move_to(p1[0],p1[1])
    152            self.ctx.line_to(p2[0],p2[1])
    153          currDash+=1
    154          dashOn = not dashOn
    155
    156 -  def addCanvasLine(self,p1,p2,color=(0,0,0),color2=None,**kwargs):
    157      self.ctx.set_line_width(kwargs.get('linewidth',1))
    158      if color2 and color2!=color:
    159        mp = (p1[0]+p2[0])/2.,(p1[1]+p2[1])/2.
    160        self.ctx.set_source_rgb(*color)
    161        self._doLine(p1,mp,**kwargs)
    162        self.ctx.stroke()
    163        self.ctx.set_source_rgb(*color2)
    164        self._doLine(mp,p2,**kwargs)
    165        self.ctx.stroke()
    166      else:
    167        self.ctx.set_source_rgb(*color)
    168        self._doLine(p1,p2,**kwargs)
    169        self.ctx.stroke()
    170
    171 -  def _addCanvasText1(self,text,pos,font,color=(0,0,0),**kwargs):
    172      if font.weight=='bold':
    173        weight=cairo.FONT_WEIGHT_BOLD
    174      else:
    175        weight=cairo.FONT_WEIGHT_NORMAL
    176      self.ctx.select_font_face(font.face,
    177                                  cairo.FONT_SLANT_NORMAL,
    178                                  weight)
    179      text = scriptPattern.sub('',text)
    180      self.ctx.set_font_size(font.size)
    181      w,h=self.ctx.text_extents(text)[2:4]
    182      bw,bh=w+h*0.4,h*1.4
    183      offset = w*pos[2]
    184      dPos = pos[0]-w/2.+offset,pos[1]+h/2.
    185      self.ctx.set_source_rgb(*color)
    186      self.ctx.move_to(*dPos)
    187      self.ctx.show_text(text)
    188
    189      if 0:
    190        self.ctx.move_to(dPos[0],dPos[1])
    191        self.ctx.line_to(dPos[0]+bw,dPos[1])
    192        self.ctx.line_to(dPos[0]+bw,dPos[1]-bh)
    193        self.ctx.line_to(dPos[0],dPos[1]-bh)
    194        self.ctx.line_to(dPos[0],dPos[1])
    195        self.ctx.close_path()
    196        self.ctx.stroke()
    197
    198      return (bw,bh,offset)
    199 -  def _addCanvasText2(self,text,pos,font,color=(0,0,0),**kwargs):
    200      if font.weight=='bold':
    201        weight=cairo.FONT_WEIGHT_BOLD
    202      else:
    203        weight=cairo.FONT_WEIGHT_NORMAL
    204      self.ctx.select_font_face(font.face,
    205                                  cairo.FONT_SLANT_NORMAL,
    206                                  weight)
    207      orientation=kwargs.get('orientation','E')
    208      cctx=pangocairo.CairoContext(self.ctx)
    209
    210      plainText = scriptPattern.sub('',text)
    211      measureLout = cctx.create_layout()
    212      measureLout.set_alignment(pango.ALIGN_LEFT)
    213      measureLout.set_markup(plainText)
    214
    215      lout = cctx.create_layout()
    216      lout.set_alignment(pango.ALIGN_LEFT)
    217      lout.set_markup(text)
    218
    219      # for whatever reason, the font size using pango is larger
    220      # than that w/ default cairo (at least for me)
    221      fnt = pango.FontDescription('%s %d'%(font.face,font.size*.8))
    222      lout.set_font_description(fnt)
    223      measureLout.set_font_description(fnt)
    224
    225      # this is a bit kludgy, but empirically we end up with too much
    226      # vertical padding if we use the text box with super and subscripts
    227      # for the measurement.
    228      iext,lext=measureLout.get_pixel_extents()
    229      iext2,lext2=lout.get_pixel_extents()
    230      w=lext2[2]-lext2[0]
    231      h=lext[3]-lext[1]
    232      pad = [h*.2,h*.3]
    233      # another empirical correction: labels draw at the bottom
    234      # of bonds have too much vertical padding
    235      if orientation=='S':
    236        pad[1] *= 0.5
    237      bw,bh=w+pad[0],h+pad[1]
    238      offset = w*pos[2]
    239      if 0:
    240        if orientation=='W':
    241          dPos = pos[0]-w+offset,pos[1]-h/2.
    242        elif orientation=='E':
    243          dPos = pos[0]-w/2+offset,pos[1]-h/2.
    244        else:
    245          dPos = pos[0]-w/2+offset,pos[1]-h/2.
    246        self.ctx.move_to(dPos[0],dPos[1])
    247      else:
    248        dPos = pos[0]-w/2.+offset,pos[1]-h/2.
    249        self.ctx.move_to(dPos[0],dPos[1])
    250
    251      self.ctx.set_source_rgb(*color)
    252      cctx.update_layout(lout)
    253      cctx.show_layout(lout)
    254
    255      if 0:
    256        self.ctx.move_to(dPos[0],dPos[1])
    257        self.ctx.line_to(dPos[0]+bw,dPos[1])
    258        self.ctx.line_to(dPos[0]+bw,dPos[1]+bh)
    259        self.ctx.line_to(dPos[0],dPos[1]+bh)
    260        self.ctx.line_to(dPos[0],dPos[1])
    261        self.ctx.close_path()
    262        self.ctx.stroke()
    263
    264
    265      return (bw,bh,offset)
    266
    267 -  def addCanvasText(self,text,pos,font,color=(0,0,0),**kwargs):
    268      if pango is not None and pangocairo is not None:
    269        textSize = self._addCanvasText2(text,pos,font,color,**kwargs)
    270      else:
    271        textSize = self._addCanvasText1(text,pos,font,color,**kwargs)
    272      return textSize
    273
    274 -  def addCanvasPolygon(self,ps,color=(0,0,0),fill=True,stroke=False,**kwargs):
    275      if not fill and not stroke: return
    276      dps = []
    277      self.ctx.set_source_rgb(*color)
    278      self.ctx.move_to(ps[0][0],ps[0][1])
    279      for p in ps[1:]:
    280        self.ctx.line_to(p[0],p[1])
    281      self.ctx.close_path()
    282      if stroke:
    283        if fill:
    284          self.ctx.stroke_preserve()
    285        else:
    286          self.ctx.stroke()
    287      if fill:
    288        self.ctx.fill()
    289
    290 -  def addCanvasDashedWedge(self,p1,p2,p3,dash=(2,2),color=(0,0,0),
    291                             color2=None,**kwargs):
    292      self.ctx.set_line_width(kwargs.get('linewidth',1))
    293      self.ctx.set_source_rgb(*color)
    294      dash = (3,3)
    295      pts1 = self._getLinePoints(p1,p2,dash)
    296      pts2 = self._getLinePoints(p1,p3,dash)
    297
    298      if len(pts2)<len(pts1): pts2,pts1=pts1,pts2
    299
    300      for i in range(len(pts1)):
    301        self.ctx.move_to(pts1[i][0],pts1[i][1])
    302        self.ctx.line_to(pts2[i][0],pts2[i][1])
    303      self.ctx.stroke()
    304
    305 -  def addCircle(self,center,radius,color=(0,0,0),fill=True,stroke=False,alpha=1.0,
    306                  **kwargs):
    307      if not fill and not stroke: return
    308      dps = []
    309      #import pdb; pdb.set_trace();
    310      self.ctx.set_source_rgba(color[0],color[1],color[2],alpha)
    311      self.ctx.arc(center[0],center[1],radius,0,2.*math.pi)
    312      self.ctx.close_path()
    313      if stroke:
    314        if fill:
    315          self.ctx.stroke_preserve()
    316        else:
    317          self.ctx.stroke()
    318      if fill:
    319        self.ctx.fill()
    320