Logo Search packages:      
Sourcecode: mascyma version File versions  Download package

backend_gd.py

00001 """
A gd backend http://newcenturycomputers.net/projects/gdmodule.html
"""

from __future__ import division
import sys, os, math

try: import gd
except ImportError:
    print >>sys.stderr, 'You must first install the gd module http://newcenturycomputers.net/projects/gdmodule.html'

from matplotlib.backend_bases import RendererBase, \
     GraphicsContextBase, FigureBase, FigureManagerBase,\
     AxisTextBase, _process_text_args, error_msg, arg_to_rgb

from matplotlib._matlab_helpers import Gcf
from matplotlib.transforms import Bound1D, Bound2D, Transform
from matplotlib import get_data_path
SCREENDPI = 75  # constant GD uses for screen DPI

00021 def error_msg_template(msg, *args):
    """
    Signal an error condition -- in a GUI, popup a error dialog
    """
    print >>sys.stderr, 'Error:', msg
    sys.exit()

    
00029 class RendererGD(RendererBase):
    """
    The renderer handles all the drawing primitives using a graphics
    context instance that controls the colors/styles
    """

    # todo: can gd support cap and join styles?
    def __init__(self, im, dpi):
        "Initialize the renderer with a gd image instance"
        self.im = im
        self._cached = {}  # a map from get_color args to colors

        self.width, self.height = im.size()
        self.dpi = dpi
00043     def draw_arc(self, gc, faceColor, x, y, width, height, angle1, angle2):
        """
        Draw an arc centered at x,y with width and height and angles
        from 0.0 to 360.0
        """
        # todo: are angles in degrees or radians?
        color = self.get_gd_color( gc.get_rgb() )
        center = int(x), self.height-int(y)
        wh = int(width), int(height)
        a1, a2 = int(angle1), int(angle2)
        
        self.im.arc(center, wh, a1, a2, color)

        if faceColor is not None:
            color = self.get_gd_color( arg_to_rgb(faceColor) )
            self.im.fill( center, color )

00060     def draw_line(self, gc, x1, y1, x2, y2):
        """
        Draw a single line from x1,y1 to x2,y2
        """
        self._set_gd_style(gc)        
        self.im.line((int(x1),self.height-int(y1)),
                     (int(x2), self.height-int(y2)),
                     gd.gdStyled)
    
00069     def draw_lines(self, gc, x, y):
        """
        x and y are equal length arrays, draw lines connecting each
        point in x, y
        """
        self._set_gd_style(gc)
        points = [ (int(thisx),self.height-int(thisy))
                   for thisx,thisy in zip(x,y)]
        self.im.lines(points, gd.gdStyled)

    
00080     def draw_polygon(self, gc, faceColor, points):
        """
        Draw a polygon.  points is a len vertices tuple, each element
        giving the x,y coords a vertex
        """

        color = self.get_gd_color( gc.get_rgb() )

        points = [(int(x), self.height-int(y)) for x,y in points]


        if faceColor is not None:
            faceColor = self.get_gd_color( arg_to_rgb(faceColor) )
            self.im.filledPolygon(points, faceColor)

        if color != faceColor:
            self.im.polygon(points, color)
            
00098     def draw_rectangle(self, gc, faceColor, x, y, width, height):
        """
        Draw a rectangle at lower left x,y with width and height
        If filled=True, fill the rectangle with the gc foreground
        gc is a GraphicsContext instance
        """

        lb = int(x), self.height-int(y)
        ur = int(x+width), self.height-int((y+height))
        color = self.get_gd_color( gc.get_rgb() )

        
        if faceColor is not None:
            faceColor = self.get_gd_color( arg_to_rgb(faceColor) )
            self.im.filledRectangle(ur, lb, faceColor)

        if color != faceColor:
            self.im.rectangle(ur, lb, color)



00119     def draw_point(self, gc, x, y):
        """
        Draw a single point at x,y
        """
        self.im.setPixel((int(x),self.height-int(y)),
                         self.get_gd_color( gc.get_rgb() ))


    def finish(self):
        pass
        #self.im.writePng( file('xx.png', 'w') )

00131     def new_gc(self):
        """
        Return an instance of a GraphicsContextGD
        """
        #size = self.im.size()
        #return GraphicsContextGD( gd.image(size) )
        return GraphicsContextGD( self.im )

00139     def get_gd_color(self, rgb):
        """
        RGB is a unit RGB tuple, return a gd color
        """

        try: return self._cached[rgb]
        except KeyError: pass
        
        r,g,b = rgb
        color = self.im.colorAllocate( (int(r*255),int(g*255),int(b*255)) )

        def dist(p1, p2):
            return math.sqrt( (p1[0]-p2[0])**2 + (p1[1]-p2[1])**2 + (p1[2]-p2[2])**2)
        if color==-1:
            
            print >>sys.stderr, 'Unable to allocate color %1.3f, %1.3f, %1.3f; using nearest neighbor' % (r, g, b)
            items = self._cached.items()
            distances = [ (dist(rgb, tup), color) for tup, color  in items]
            distances.sort()
            distance, color = distances[0]
        self._cached[rgb] = color
        return color

    def _set_gd_style(self, gc):
        color = self.get_gd_color( gc.get_rgb() )
        offset, dashes = gc.get_dashes()

        if dashes is not None:
            dashes = self.dpi.get()/72.0*dashes
            style = []
            for on, off in zip(dashes[:-1], dashes[1:]):
                style.extend([color]*int(on))
                style.extend([gd.gdTransparent]*int(off))
        else: style=[color]
        self.im.setStyle(style)

        
00176 class GraphicsContextGD(GraphicsContextBase):
    """
    The graphics context provides the color, line styles, etc...  See
    the gtk and postscript backends for examples of mapping the
    graphics context attributes (cap styles, join styles, line widths,
    colors) to a particular backend.      """
00182     def __init__(self, im):
        """
        Initialize with a gd image
        """
        GraphicsContextBase.__init__(self)
        self.im = im
        
    def set_linestyle(self, style):
        GraphicsContextBase.set_linestyle(self, style)
        offset, dashes = self._dashd[style]
        self.set_dashes(offset, dashes)

              
00195 class FigureGD(FigureBase):
    """
    The figure is the main object -- it contains the Axes and Text,
    which in turn contain lines, patches, and more text
    """
    def __init__(self, figsize, dpi, facecolor='w', edgecolor='w'):
        dpi = 72.0/SCREENDPI*dpi
        FigureBase.__init__(self, figsize, dpi, facecolor, edgecolor)
        self._isRealized = False

        white = 1,1,1
        self._figurePatch.set_facecolor( white )
        self._figurePatch.set_edgecolor( white)

00209     def draw(self, drawable, *args, **kwargs):
        """
        Render the figure using RendererGD instance drawable
        """
        # draw the figure bounding box, perhaps none for white figure

        self._figurePatch.draw(drawable)

        # render the axes
        for a in self.axes:
            a.wash_brushes()
            a.draw(drawable)

        # render the figure text
        for t in self._text:
            t.draw(drawable)
    
00226     def print_figure(self, filename, dpi):
        """
        Render the figure to hardcopy using self.drawable as the
        renderer if neccessary

        # todo -- how should we handle dpi?
        """
        dpi = 72.0/SCREENDPI*dpi
        oldDPI = self.dpi.get()
        scale = dpi/oldDPI
        xmax = self.bbox.x.max()
        ymax = self.bbox.y.max()
        self.bbox.x.set_max(scale*xmax)
        self.bbox.y.set_max(scale*ymax)
        
        self.dpi.set(dpi)
        left, bottom, width, height = self.bbox.get_bounds()
        im = gd.image((int(width), int(height)))
        drawable = RendererGD(im, self.dpi)
        for a in self.axes:
            a.set_child_attr('_im', im)
            a.resize()

        for t in self._text:
            t.set_child_attr('_im', im)
            
        self.draw(drawable)
        drawable.finish()

        basename, ext = os.path.splitext(filename)
        if not len(ext): filename += '.png'
        im.writePng( file(filename, 'wb') )
        # todo allow write jpg based on identified extension
        # allow write to ps based on extension


00262     def text(self, x, y, s, *args, **kwargs):
        """
        Add text to figure at location x,y (relative 0-1 coords) See
        the help for Axis text for the meaning of the other arguments
        """

        override = _process_text_args({}, *args, **kwargs)
        t = AxisTextGD(
            self.dpi, self.bbox,
            x=x, y=y, text=s,
            transx = Transform(Bound1D(0,1), self.bbox.x),
            transy = Transform(Bound1D(0,1), self.bbox.y),
            **override)
        self._text.append(t)
        return t




00281 class AxisTextGD(AxisTextBase):    
    """
    Handle storing and drawing of text
    """

    def __init__(self, *args, **kwargs):                                  
        AxisTextBase.__init__(self, *args, **kwargs)
        self._font = fontManager.findfont(
            self._fontname, self._fontweight, self._fontangle)
        
00291     def _compute_offsets(self):
        """
        Return the (x,y) offsets to adjust for the alignment
        specifications
        """
        scale = self.dpi.get()/72.0
        try:
            llx, lly, lrx, lry, urx, ury, ulx, uly = \
                 self._im.get_bounding_rect(
                self._font, scale*self._fontsize, 0.0, (0,0), self._text)
        except ValueError:
            error_msg('Could not load font %s.  Try setting GDFONTPATH to include this font' % self._font)
            
        w = lrx - llx
        h = lly - uly

        #print self._text, llx, lrx, lry, ury
        if self._rotation=='vertical':
            w, h = h, w
            if self._horizontalalignment=='center': offsetx = w/2
            elif self._horizontalalignment=='right': offsetx = 0
            else: offsetx = w

            if self._verticalalignment=='center': offsety = -h/2
            elif self._verticalalignment=='top': offsety = -h
            else: offsety = 0
        else:
            if self._horizontalalignment=='center': offsetx = -w/2
            elif self._horizontalalignment=='right': offsetx = -w
            else: offsetx = 0

            if self._verticalalignment=='center': offsety = -h/2
            elif self._verticalalignment=='top': offsety = -h
            else: offsety = 0

        return (offsetx, offsety)

00328     def _draw(self, drawable, *args, **kwargs):
        """
        Render the text using the RendererGD instance
        """
        x,y = self._get_xy_pos()
        y = drawable.height - y
        
        gc = drawable.new_gc()
        gc.set_foreground(self._color)
        color = drawable.get_gd_color( gc.get_rgb() )

        if self._rotation=='vertical': angle=math.pi/2.0
        else: angle = 0

        scale = self.dpi.get()/SCREENDPI
        self._im.string_ft(self._font, scale*self._fontsize, angle,
                            (x,y), self._text, color)

        self._drawable = drawable
         
    def _get_xy_pos(self):
        if self._reset: self._set_font()
        x, y = self._get_xy_display()
        ox, oy = self._compute_offsets()
        pos = int(x+ox), int(y+oy)
        return pos
    
    def get_window_extent(self):

        # there is a very small error in the x window extent.  Don't
        # know where it is coming from right now
        x,y = self._get_xy_pos()
        scale = self.dpi.get()/SCREENDPI
        llx, lly, lrx, lry, urx, ury, ulx, uly = self._im.get_bounding_rect(
            self._font, scale*self._fontsize, 0.0, (x,y), self._text) 
        w = lrx - llx
        h = lly - uly
        if self._rotation=='vertical':
            w,h = h,w
            x = x - w
        bbox = Bound2D(x, y, w, h)
        return bbox
    

00372     def _set_font(self):
        """
        Update any font information; this function will be called if
        font state (eg, fontsize, fontweight) has been changed
        """
        self._font = fontManager.findfont(
            self._fontname, self._fontweight, self._fontangle)
        
########################################################################
#    
# The following functions and classes are for matlab compatibility
# mode (matplotlib.matlab) and implement figure managers, etc...
#
########################################################################

def draw_if_interactive():  pass

def show(): pass

00391 def new_figure_manager(num, figsize, dpi):
    """
    Add a new figure num (default autoincrement).  For GUI
    backends, you'll need to instantiate a new window and embed
    the figure in it.
    """
    thisFig = FigureGD(figsize, dpi)
    figwin = FigureManagerGD(thisFig, num)
    return figwin


00402 class FigureManagerGD(FigureManagerBase):
    """
    This class manages all the figures for matlab mode
    """
    pass

class FontManager:
    from ttfquery import ttffiles, describe
    from ttfquery._scriptregistry import registry, registryFile

    paths = [get_data_path()]
 
    if os.environ.has_key('GDFONTPATH'):
        gdpath = os.environ['GDFONTPATH']
        if gdpath.find(';')>0: #win32 style
            paths.extend(gdpath.split(';'))
        elif gdpath.find(':')>0: # unix style
            paths.extend(gdpath.split(':'))
        else:
            paths.append(gdpath)
    #print 'PATHS', paths
    new, failed = registry.scan( paths, printErrors = 1, force = 0)
    registry.save(registryFile)

    cache = {}

    def findfont(self, fontname, weight, italics):
        origname = fontname
        fontname = fontname.lower()

        if italics.find('ital')>=0: italics = 1
        elif italics.find('oblique')>=0: italics = 1 # todo fixme for oblique
        else: italics = None
        
        try: return self.cache[ (fontname, weight, italics) ]
        except KeyError: pass
        
        for name, table in self.registry.fonts.items():
            if name.lower().find(fontname)<0: continue

            items = table.items()
            items.sort()

            if weight is not None:
                weight = self.describe.weightNumber( weight )
                items = [item for item in items if item[0][0]==weight]
            if italics is not None:
                items = [item for item in items if item[0][1]==italics]
            if len(items):
                match = items[0][1][0]
                fullpath = self.registry.fontFile( match )
                head, tail = os.path.split(fullpath)
                fname, ext = os.path.splitext(tail)
                self.cache[ (fontname, weight, italics) ] = fullpath
                return fullpath
        default = self.get_default_font(origname)
        self.cache[ (fontname, weight, italics) ] = default
        return default

    def get_default_font(self, fontname):
        print >>sys.stderr, 'Could not find requested font %s\n' % fontname + \
        'Please set environment var GDFONTPATH to point to your true type fonts (*.ttf)'

        if fontname.lower()=='vera':
            error_msg('Could not find any fonts or the default Vera\n' +
                      'Please check your GDFONTPATH')
        print >>sys.stderr, 'Could not find %s; falling back on Vera' % fontname
        return os.path.join(get_data_path(),  'Vera.ttf')


fontManager = FontManager()

########################################################################
#    
# Now just provide the standard names that backend.__init__ is expecting
# 
########################################################################

FigureManager = FigureManagerGD
AxisText = AxisTextGD
Figure = FigureGD
error_msg = error_msg_template
         

Generated by  Doxygen 1.6.0   Back to index