def test_path(): from aggdraw import Draw, Path Path() Path([0, 0]) Path([0, 0, 0, 0]) p = Path() p.moveto(0, 0) p.lineto(1, 1) assert p.coords() == [0.0, 0.0, 1.0, 1.0] p.curveto(0, 0, 0, 0, 0, 0) p.close() p.coords() assert p.coords() == [0.0, 0.0, 1.0, 1.0, 0.125, 0.125, 0.0, 0.0] draw = Draw("RGB", (800, 600)) draw.line(p) draw.polygon(p) draw.symbol((0, 0), p)
def test_path(): from aggdraw import Draw, Path Path() Path([0, 0]) Path([0, 0, 0, 0]) p = Path() p.moveto(0, 0) p.lineto(1, 1) assert p.coords() == [0.0, 0.0, 1.0, 1.0] p.curveto(2, 1, 2, 1, 2, 0) p.close() p.coords() # Changed test w.r.t. aggdraw for agg 2.4 # Correctness of this may be seen in the file test-path-proof.ps assert p.coords() == [0.0, 0.0, 1.0, 1.0, 1.625, 1.0, 2.0, 0.625, 2.0, 0.0] draw = Draw("RGB", (800, 600)) draw.line(p) draw.polygon(p) draw.symbol((0, 0), p)
def test_graphics(): from aggdraw import Draw, Pen, Brush draw = Draw("RGB", (500, 500)) pen = Pen("black") brush = Brush("black") draw.line((50, 50, 100, 100), pen) draw.rectangle((50, 150, 100, 200), pen) draw.rectangle((50, 220, 100, 270), brush) draw.rectangle((50, 290, 100, 340), brush, pen) draw.rectangle((50, 360, 100, 410), pen, brush) draw.ellipse((120, 150, 170, 200), pen) draw.ellipse((120, 220, 170, 270), brush) draw.ellipse((120, 290, 170, 340), brush, pen) draw.ellipse((120, 360, 170, 410), pen, brush) draw.polygon((190+25, 150, 190, 200, 190+50, 200), pen) draw.polygon((190+25, 220, 190, 270, 190+50, 270), brush) draw.polygon((190+25, 290, 190, 340, 190+50, 340), brush, pen) draw.polygon((190+25, 360, 190, 410, 190+50, 410), pen, brush)
class Canvas(CanvasBase): # fonts appear smaller in aggdraw than with cairo # fix that here: fontScale = 1.2 def __init__(self, img=None, imageType=None, # determines file type fileName=None, # if set determines output file name size=None, ): if img is None: try: import Image except ImportError: from PIL import Image if size is None: raise ValueError('please provide either an image or a size') img = Image.new('RGBA', size, "white") self.image = img self.draw = Draw(img) self.draw.setantialias(True) if size is None: self.size = self.draw.size else: self.size = size if imageType and imageType not in ('png', 'jpg'): raise ValueError('unsupported image type for agg canvas') self.drawType = imageType self.fileName = fileName def _doLine(self, p1, p2, pen, **kwargs): if kwargs.get('dash', (0, 0)) == (0, 0): self.draw.line((p1[0], p1[1], p2[0], p2[1]), pen) else: dash = kwargs['dash'] pts = self._getLinePoints(p1, p2, dash) currDash = 0 dashOn = True while currDash < (len(pts) - 1): if dashOn: p1 = pts[currDash] p2 = pts[currDash + 1] self.draw.line((p1[0], p1[1], p2[0], p2[1]), pen) currDash += 1 dashOn = not dashOn def addCanvasLine(self, p1, p2, color=(0, 0, 0), color2=None, **kwargs): if color2 and color2 != color: mp = (p1[0] + p2[0]) / 2., (p1[1] + p2[1]) / 2. color = convertColor(color) self._doLine(p1, mp, Pen(color, kwargs.get('linewidth', 1)), **kwargs) color2 = convertColor(color2) self._doLine(mp, p2, Pen(color2, kwargs.get('linewidth', 1)), **kwargs) else: color = convertColor(color) self._doLine(p1, p2, Pen(color, kwargs.get('linewidth', 1)), **kwargs) def addCanvasText(self, text, pos, font, color=(0, 0, 0), **kwargs): orientation = kwargs.get('orientation', 'E') color = convertColor(color) aggFont = Font(color, faceMap[font.face], size=font.size * self.fontScale) blocks = list(re.finditer(r'\<(.+?)\>(.+?)\</\1\>', text)) w, h = 0, 0 supH = 0 subH = 0 if not len(blocks): w, h = self.draw.textsize(text, aggFont) tw, th = w, h offset = w * pos[2] dPos = pos[0] - w / 2. + offset, pos[1] - h / 2. self.draw.text(dPos, text, aggFont) else: dblocks = [] idx = 0 for block in blocks: blockStart, blockEnd = block.span(0) if blockStart != idx: # untagged text: tblock = text[idx:blockStart] tw, th = self.draw.textsize(tblock, aggFont) w += tw h = max(h, th) dblocks.append((tblock, '', tw, th)) fmt = block.groups()[0] tblock = block.groups()[1] if fmt in ('sub', 'sup'): lFont = Font(color, faceMap[font.face], size=0.8 * font.size * self.fontScale) else: lFont = aggFont tw, th = self.draw.textsize(tblock, lFont) w += tw if fmt == 'sub': subH = max(subH, th) elif fmt == 'sup': supH = max(supH, th) else: h = max(h, th) dblocks.append((tblock, fmt, tw, th)) idx = blockEnd if idx != len(text): # untagged text: tblock = text[idx:] tw, th = self.draw.textsize(tblock, aggFont) w += tw h = max(h, th) dblocks.append((tblock, '', tw, th)) supH *= 0.5 subH *= 0.5 h += supH + subH offset = w * pos[2] if orientation == 'W': dPos = [pos[0] - w + offset, pos[1] - h / 2.] elif orientation == 'E': dPos = [pos[0] + offset, pos[1] - h / 2.] else: dPos = [pos[0] - w / 2. + offset, pos[1] - h / 2.] if supH: dPos[1] += supH for txt, fmt, tw, th in dblocks: tPos = dPos.copy() if fmt == 'sub': tPos[1] += subH elif fmt == 'sup': tPos[1] -= supH if fmt in ('sub', 'sup'): lFont = Font(color, faceMap[font.face], size=0.8 * font.size * self.fontScale) else: lFont = aggFont self.draw.text(tPos, txt, lFont) dPos[0] += tw return (tw + th * .4, th + th * .4, offset) def addCanvasPolygon(self, ps, color=(0, 0, 0), fill=True, stroke=False, **kwargs): if not fill and not stroke: return dps = [] for p in ps: dps.extend(p) color = convertColor(color) brush = None pen = None if fill: brush = Brush(color) if stroke: pen = Pen(color) self.draw.polygon(dps, pen, brush) def addCanvasDashedWedge(self, p1, p2, p3, dash=(2, 2), color=(0, 0, 0), color2=None, **kwargs): pen = Pen(color, kwargs.get('linewidth', 1)) dash = (3, 3) pts1 = self._getLinePoints(p1, p2, dash) pts2 = self._getLinePoints(p1, p3, dash) if len(pts2) < len(pts1): pts2, pts1 = pts1, pts2 for i in range(len(pts1)): self.draw.line((pts1[i][0], pts1[i][1], pts2[i][0], pts2[i][1]), pen) def flush(self): self.draw.flush() if self.fileName: self.image.save(self.fileName)
class Canvas(CanvasBase): # fonts appear smaller in aggdraw than with cairo # fix that here: fontScale=1.2 def __init__(self, img=None, imageType=None, # determines file type fileName=None, # if set determines output file name size=None, ): if img is None: import Image if size is None: raise ValueError,'please provide either an image or a size' img = Image.new('RGBA',size,"white") self.image = img self.draw = Draw(img) self.draw.setantialias(True) if size is None: self.size = self.draw.size else: self.size = size if imageType and imageType not in ('png','jpg'): raise ValueError,'unsupported image type for agg canvas' self.drawType=imageType self.fileName=fileName def _doLine(self, p1, p2, pen, **kwargs): if kwargs.get('dash',(0,0)) == (0,0): self.draw.line((p1[0],p1[1],p2[0],p2[1]),pen) else: dash = kwargs['dash'] pts = self._getLinePoints(p1,p2,dash) currDash = 0 dashOn = True while currDash<(len(pts)-1): if dashOn: p1 = pts[currDash] p2 = pts[currDash+1] self.draw.line((p1[0],p1[1],p2[0],p2[1]),pen) currDash+=1 dashOn = not dashOn def addCanvasLine(self, p1, p2, color=(0,0,0), color2=None, **kwargs): if color2 and color2!=color: mp = (p1[0]+p2[0])/2.,(p1[1]+p2[1])/2. color = convertColor(color) self._doLine(p1,mp,Pen(color,kwargs.get('linewidth',1)),**kwargs) color2 = convertColor(color2) self._doLine(mp,p2,Pen(color2,kwargs.get('linewidth',1)),**kwargs) else: color = convertColor(color) self._doLine(p1,p2,Pen(color,kwargs.get('linewidth',1)),**kwargs) def addCanvasText(self,text,pos,font,color=(0,0,0),**kwargs): orientation=kwargs.get('orientation','E') color = convertColor(color) aggFont = Font(color,faceMap[font.face],size=font.size*self.fontScale) blocks = list(re.finditer(r'\<(.+?)\>(.+?)\</\1\>',text)) w,h = 0,0 supH=0 subH=0 if not len(blocks): w,h=self.draw.textsize(text,aggFont) bw,bh=w*1.1,h*1.1 dPos = pos[0]-bw/2.,pos[1]-bh/2. bgColor=kwargs.get('bgColor',(1,1,1)) bgColor = convertColor(bgColor) self.draw.rectangle((dPos[0],dPos[1],dPos[0]+bw,dPos[1]+bh), None,Brush(bgColor)) dPos = pos[0]-w/2.,pos[1]-h/2. self.draw.text(dPos,text,aggFont) else: dblocks=[] idx=0 for block in blocks: blockStart,blockEnd=block.span(0) if blockStart != idx: # untagged text: tblock = text[idx:blockStart] tw,th=self.draw.textsize(tblock,aggFont) w+=tw h = max(h,th) dblocks.append((tblock,'',tw,th)) fmt = block.groups()[0] tblock = block.groups()[1] if fmt in ('sub','sup'): lFont = Font(color,faceMap[font.face],size=0.8*font.size*self.fontScale) else: lFont = aggFont tw,th=self.draw.textsize(tblock,lFont) w+=tw if fmt == 'sub': subH = max(subH,th) elif fmt=='sup': supH = max(supH,th) else: h = max(h,th) dblocks.append((tblock,fmt,tw,th)) idx = blockEnd if idx!=len(text): # untagged text: tblock = text[idx:] tw,th=self.draw.textsize(tblock,aggFont) w+=tw h = max(h,th) dblocks.append((tblock,'',tw,th)) supH *= 0.25 subH *= 0.25 h += supH + subH bw,bh=w*1.1,h #dPos = pos[0]-bw/2.,pos[1]-bh/2. dPos = [pos[0]-w/2.,pos[1]-h/2.] if orientation=='W': dPos = [pos[0]-w,pos[1]-h/2.] elif orientation=='E': dPos = [pos[0],pos[1]-h/2.] else: dPos = [pos[0]-w/2,pos[1]-h/2.] bgColor=kwargs.get('bgColor',(1,1,1)) bgColor = convertColor(bgColor) self.draw.rectangle((dPos[0],dPos[1],dPos[0]+bw,dPos[1]+bh), None,Brush(bgColor)) if supH: dPos[1]+=supH for txt,fmt,tw,th in dblocks: tPos = dPos[:] if fmt=='sub': tPos[1]+=subH elif fmt=='sup': tPos[1]-=supH if fmt in ('sub','sup'): lFont = Font(color,faceMap[font.face],size=0.8*font.size*self.fontScale) else: lFont = aggFont self.draw.text(tPos,txt,lFont) dPos[0]+=tw def addCanvasPolygon(self,ps,color=(0,0,0),fill=True,stroke=False,**kwargs): if not fill and not stroke: return dps = [] for p in ps: dps.extend(p) color = convertColor(color) brush=None pen=None if fill: brush = Brush(color) if stroke: pen = Pen(color) self.draw.polygon(dps,pen,brush) def addCanvasDashedWedge(self,p1,p2,p3,dash=(2,2),color=(0,0,0), color2=None,**kwargs): pen = Pen(color,kwargs.get('linewidth',1)) dash = (3,3) pts1 = self._getLinePoints(p1,p2,dash) pts2 = self._getLinePoints(p1,p3,dash) if len(pts2)<len(pts1): pts2,pts1=pts1,pts2 for i in range(len(pts1)): self.draw.line((pts1[i][0],pts1[i][1],pts2[i][0],pts2[i][1]),pen) def flush(self): self.draw.flush() if self.fileName: self.image.save(self.fileName)
class ELCustomDisplay(pylink.EyeLinkCustomDisplay, EnvAgent): #TODO: add scaling support for images without ruining performance (OpenGL scale?) def __init__(self): EnvAgent.__init__(self) self.size = (0, 0) self.imagebuffer = [] self.palette = [] self.img = None # PIL.Image self.drawer = None # aggdraw Draw with self.img as context self.title = None self.txtm.add_style("el_setup", "20px", P.default_color, font_label="Hind-Medium") self.dc_target = drift_correct_target() pylink.EyeLinkCustomDisplay.__init__(self) # If using an EyeLink 1000 or newer, these commands need to be sent # to the tracker for everything to work correctly if self.el.getTrackerVersion() >= EYELINK_1000: self.el.sendCommand("enable_search_limits=YES") self.el.sendCommand("track_search_limits=YES") self.el.sendCommand("autothreshold_click=YES") self.el.sendCommand("autothreshold_repeat=YES") self.el.sendCommand("enable_camera_position_detect=YES") # Define dict mapping sdl2 keycodes to pylink keycodes self.pylink_keycodes = dict([(sdl2.SDLK_F1, pylink.F1_KEY), (sdl2.SDLK_F2, pylink.F2_KEY), (sdl2.SDLK_F3, pylink.F3_KEY), (sdl2.SDLK_F4, pylink.F4_KEY), (sdl2.SDLK_F5, pylink.F5_KEY), (sdl2.SDLK_F6, pylink.F6_KEY), (sdl2.SDLK_F7, pylink.F7_KEY), (sdl2.SDLK_F8, pylink.F8_KEY), (sdl2.SDLK_F9, pylink.F9_KEY), (sdl2.SDLK_F10, pylink.F10_KEY), (sdl2.SDLK_PAGEUP, pylink.PAGE_UP), (sdl2.SDLK_PAGEDOWN, pylink.PAGE_DOWN), (sdl2.SDLK_UP, pylink.CURS_UP), (sdl2.SDLK_DOWN, pylink.CURS_DOWN), (sdl2.SDLK_LEFT, pylink.CURS_LEFT), (sdl2.SDLK_RIGHT, pylink.CURS_RIGHT), (sdl2.SDLK_RETURN, pylink.ENTER_KEY), (sdl2.SDLK_ESCAPE, pylink.ESC_KEY), (sdl2.SDLK_BACKSPACE, ord('\b')), (sdl2.SDLK_TAB, ord('\t'))]) # Define dict mapping pylink colour constants to RGB colours self.pylink_colors = [ (0, 0, 0), # 0 = placeholder (transparent) (255, 255, 255), # 1 = pylink.CR_HAIR_COLOR (white) (255, 255, 255), # 2 = pylink.PUPIL_HAIR_COLOR (white) (0, 255, 0), # 3 = pylink.PUPIL_BOX_COLOR (green) (255, 0, 0), # 4 = pylink.SEARCH_LIMIT_BOX_COLOR (red) (255, 0, 0) # 5 = pylink.MOUSE_CURSOR_COLOR (red) ] try: self.__target_beep__ = AudioClip("target_beep.wav") self.__target_beep__done__ = AudioClip("target_beep_done.wav") self.__target_beep__error__ = AudioClip("target_beep_error.wav") except: self.__target_beep__ = None self.__target_beep__done__ = None self.__target_beep__error__ = None def record_abort_hide(self): pass def clear_cal_display(self): fill() flip() fill() def setup_cal_display(self): self.clear_cal_display() def exit_cal_display(self): self.clear_cal_display() def draw_cal_target(self, x, y=None, pump_events=True): fill() if pump_events: pump() if y is None: y = x[1] x = x[0] blit(self.dc_target, 5, (int(x), int(y))) flip() def erase_cal_target(self): self.clear_cal_display() def play_beep(self, clip): try: if clip in [pylink.DC_TARG_BEEP, pylink.CAL_TARG_BEEP]: self.__target_beep__.play() elif clip in [pylink.CAL_ERR_BEEP, pylink.DC_ERR_BEEP]: self.__target_beep__error__.play() else: self.__target_beep__done__.play() except: pass def get_input_key(self): keys = [] for event in pump(True): if event.type == sdl2.SDL_KEYDOWN: keysym = event.key.keysym if not self.el._quitting: # don't process quit requests while already quitting ui_request(keysym) try: key = self.pylink_keycodes[keysym.sym] except KeyError: key = keysym.sym # don't allow escape to control tracker unless calibrating if key == pylink.ESC_KEY and not self.el.in_setup: key = pylink.JUNK_KEY keys.append(pylink.KeyInput(key, keysym.mod)) return keys def get_mouse_state(self): x, y, b = mouse_pos(pump_event_queue=False, return_button_state=True) x = int(x) - (P.screen_c[0] - self.size[0] / 2) y = int(y) - (P.screen_c[1] - self.size[1] / 2) # Restrict mouse coords to within bounds of camera image x = clip(x, minimum=0, maximum=self.size[0]) y = clip(y, minimum=0, maximum=self.size[1]) if b != 1: # Register left clicks only b = 0 return ((x, y), b) def alert_printf(self, message): print("EyeLink Alert: {0}".format(message)) def setup_image_display(self, width, height): '''Sets camera image to the provided size, returns 1 on success.''' self.size = (width, height) self.clear_cal_display() return 1 def exit_image_display(self): self.clear_cal_display() def image_title(self, text): self.title = message(text, "el_setup", blit_txt=False) def set_image_palette(self, r, g, b): ''' Sets the palette to use for the camera image and clears the image buffer. Converts r,g,b (lists containing the RGB palette) to a list of colours ([R,G,B,R,G,B,...]) that can be used by PIL.Image. ''' self.imagebuffer = [] self.palette = list(sum(zip(r, g, b), ())) def draw_image_line(self, width, line, totlines, buff): ''' Reads in the buffer from the EyeLink camera image line by line and writes it into a buffer of size (width * totlines). Once the last line of the image has been read into the buffer, the image buffer is placed in a PIL.Image with the palette set by set_image_palette, converted to RGBA, resized, and then rendered to the middle of the screen. After rendering, the image buffer is cleared. ''' if len(self.imagebuffer) > (width * totlines): self.imagebuffer = [] self.imagebuffer += buff if int(line) == int(totlines): # Render complete camera image and resize to self.size img = Image.new("P", (width, totlines), 0) img.putpalette(self.palette) img.putdata(self.imagebuffer) self.img = img.convert('RGBA').resize(self.size, Image.BILINEAR) # Set up aggdraw to draw crosshair/bounds/etc. on image surface self.drawer = Draw(self.img) self.drawer.setantialias(True) self.draw_cross_hair() self.drawer.flush() # Draw complete image to screen fill() blit(asarray(self.img), 5, P.screen_c) if self.title: loc_x = (P.screen_c[0]) loc_y = (P.screen_c[1] + self.size[1] / 2 + 20) blit(self.title, 8, (loc_x, loc_y)) flip() # Clear image buffer self.imagebuffer = [] def draw_lozenge(self, x, y, width, height, colorindex): lozenge_pen = Pen(self.pylink_colors[colorindex], 3, 255) if width > height: gap = width - height middle = x + width / 2.0 arc_left = (x, y, x + height, y + height) arc_right = (x + gap, y, x + width, y + height) line_top = (floor(middle - gap / 2.0), y, ceil(middle + gap / 2.0), y) line_bottom = (floor(middle - gap / 2.0), y + height, ceil(middle + gap / 2.0), y + height) self.drawer.arc(arc_left, 90, 270, lozenge_pen) self.drawer.arc(arc_right, -90, 90, lozenge_pen) self.drawer.line(line_top, lozenge_pen) self.drawer.line(line_bottom, lozenge_pen) elif height > width: gap = height - width middle = y + height / 2.0 arc_top = (x, y, x + width, y + width) arc_bottom = (x, y + gap, x + width, y + height) line_left = (x, floor(middle - gap / 2.0), x, ceil(middle + gap / 2.0)) line_right = (x + width, floor(middle - gap / 2.0), x + width, ceil(middle + gap / 2.0)) self.drawer.arc(arc_top, 0, 180, lozenge_pen) self.drawer.arc(arc_bottom, 180, 360, lozenge_pen) self.drawer.line(line_left, lozenge_pen) self.drawer.line(line_right, lozenge_pen) else: self.drawer.ellipse((x, y, x + width, y + height), lozenge_pen) def draw_line(self, x1, y1, x2, y2, colorindex): line_pen = Pen(self.pylink_colors[colorindex], 3, 255) self.drawer.line((x1, y1, x2, y2), line_pen)