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