Esempio n. 1
0
class FixedString(Texture):
    """
  A texture containing a simple string drawn using ImageDraw.
  
  The advantage over a standard String is that it only requires a simple
  Sprite shape for drawing so the gpu has to only draw two triangles
  rather than two triangles for each letter."""
    def __init__(self,
                 font,
                 string,
                 camera=None,
                 color=(255, 255, 255, 255),
                 shadow=(0, 0, 0, 255),
                 shadow_radius=0,
                 font_size=24,
                 margin=5.0,
                 justify='C',
                 background_color=None,
                 shader=None,
                 f_type='',
                 mipmap=True):
        """Arguments:
    
    *font*:
      File path/name to a TrueType font file.

    *string*:
      String to write.
      
    *camera*:
      Camera object passed on to constructor of sprite
      
    *color*:
      Color in format '#RRGGBB', (255,0,0,255), 'orange' etc (as accepted 
      by PIL.ImageDraw) default (255, 255, 255, 255) i.e. white 100% alpha

    *shadow*:
      Color of shadow, default black.

    *shadow_radius*:
      Gaussian blur radius applied to shadow layer, default 0 (no shadow)

    *font_size*:
      Point size for drawing the letters on the internal Texture. default 24

    *margin*:
      Offsets from the top left corner for the text and space on right
      and bottom. default 5.0
      
    *justify*:
      L(eft), C(entre), R(ight) default C

    *background_color*:
      filled background in ImageDraw format as above. default None i.e.
      transparent. 
      
    *shader*:
      can be passed to init otherwise needs to be set in set_shader or
      draw. default None
      
    *f_type*:
      filter type. BUMP will generate a normal map (indented by default,
      +BUMP or BUMP+ will make it stick out), EMBOSS, CONTOUR, BLUR and 
      SMOOTH do what they sound like they will do.
    """
        super(FixedString, self).__init__(font, mipmap=mipmap)
        self.font = font
        try:
            imgfont = ImageFont.truetype(font, font_size)
        except IOError:
            abspath = os.path.abspath(font)
            msg = "Couldn't find font file '%s'" % font
            if font != abspath:
                msg = "%s - absolute path is '%s'" % (msg, abspath)
            raise Exception(msg)

        justify = justify.upper()
        f_type = f_type.upper()
        ascent, descent = imgfont.getmetrics()
        height = ascent + descent
        lines = string.split('\n')
        nlines = len(lines)
        maxwid = 0
        for l in lines:
            line_wid = imgfont.getsize(l)[0]
            if line_wid > maxwid:
                maxwid = line_wid
        maxwid += 2.0 * margin
        texture_wid = WIDTHS[0]
        for l in WIDTHS:
            if l > maxwid:
                texture_wid = l
                break
            texture_wid = l
        texture_hgt = int(nlines * height + 2 * margin)

        self.im = Image.new("RGBA", (texture_wid, texture_hgt),
                            background_color)
        self.ix, self.iy = texture_wid, texture_hgt
        draw = ImageDraw.Draw(self.im)
        if shadow_radius > 0:
            from PIL import ImageFilter
            self._render_text(lines, justify, margin, imgfont, maxwid, height,
                              shadow, draw)
            self.im = self.im.filter(
                ImageFilter.GaussianBlur(radius=shadow_radius))
            if background_color == None:
                im_arr = self._force_color(np.array(self.im), shadow)
                try:  # numpy not quite working fully in pypy so have to convert tobytes
                    self.im = Image.fromarray(im_arr)
                except:
                    h, w, c = im_arr.shape
                    rgb = 'RGB' if c == 3 else 'RGBA'
                    self.im = Image.frombytes(rgb, (w, h), im_arr.tobytes())

            draw = ImageDraw.Draw(self.im)

        self._render_text(lines, justify, margin, imgfont, maxwid, height,
                          color, draw)

        force_color = background_color is None and shadow_radius == 0
        if f_type == '':
            self.image = np.array(self.im)
        elif 'BUMP' in f_type:
            amount = -1.0 if '+' in f_type else 1.0
            self.image = self._normal_map(np.array(self.im, dtype=np.uint8),
                                          amount)
            force_color = False

        else:
            from PIL import ImageFilter
            if f_type == 'EMBOSS':
                self.im = self.im.filter(ImageFilter.EMBOSS)
            elif f_type == 'CONTOUR':
                self.im = self.im.filter(ImageFilter.CONTOUR)
            elif f_type == 'BLUR':
                self.im = self.im.filter(ImageFilter.BLUR)
            elif f_type == 'SMOOTH':
                self.im = self.im.filter(ImageFilter.SMOOTH_MORE)
            self.image = np.array(self.im)

        if force_color:
            self.image = self._force_color(self.image, color)

        self._tex = ctypes.c_uint()

        bmedge = nlines * height + 2.0 * margin
        self.sprite = Sprite(camera=camera, w=maxwid, h=bmedge)
        buf = self.sprite.buf[0]  #convenience alias
        buf.textures = [self]
        if shader != None:
            self.sprite.shader = shader
            buf.shader = shader
        buf.unib[6] = float(maxwid / texture_wid)  #scale to fit
        buf.unib[7] = float(bmedge / texture_hgt)

    def _render_text(self, lines, justify, margin, imgfont, maxwid, height,
                     color, draw):
        for i, line in enumerate(lines):
            line_len = imgfont.getsize(line)[0]
            if justify == "C":
                xoff = (maxwid - line_len) / 2.0
            elif justify == "L":
                xoff = margin
            else:
                xoff = maxwid - line_len

            draw.text((xoff, margin + i * height),
                      line,
                      font=imgfont,
                      fill=color)

    def set_shader(self, shader):
        ''' wrapper for Shape.set_shader'''
        self.sprite.set_shader(shader)

    def draw(self,
             shader=None,
             txtrs=None,
             ntl=None,
             shny=None,
             camera=None,
             mlist=[]):
        '''wrapper for Shape.draw()'''
        self.sprite.draw(shader, txtrs, ntl, shny, camera, mlist)

    def _force_color(self, im_array, color):
        """
    Overwrite color of all pixels as PIL renders text incorrectly when drawing on transparent backgrounds
    http://nedbatchelder.com/blog/200801/truly_transparent_text_with_pil.html
    """
        if isinstance(color, str):
            from PIL import ImageColor
            color = ImageColor.getrgb(color)
        im_array[:, :, :3] = color[:3]
        return im_array

    def _load_disk(self):
        """
Esempio n. 2
0
class FixedOutlineString(Texture):
    """
  A texture containing a simple string drawn using ImageDraw.

  The advantage over a standard String is that it only requires a simple
  Sprite shape for drawing so the gpu has to only draw two triangles
  rather than two triangles for each letter."""
    def __init__(self,
                 font,
                 string,
                 camera=None,
                 color=(255, 255, 255, 255),
                 outline=(0, 0, 0, 255),
                 outline_size=0,
                 font_size=24,
                 margin=5.0,
                 justify='C',
                 background_color=None,
                 shader=None,
                 f_type='',
                 mipmap=True):
        """Arguments:

    *font*:
      File path/name to a TrueType font file.

    *string*:
      String to write.

    *camera*:
      Camera object passed on to constructor of sprite

    *color*:
      Color in format #RRGGBB, (255,0,0,255) etc (as accepted by PIL.ImageDraw)
      default (255, 255, 255, 255) i.e. white 100% alpha

    *font_size*:
      Point size for drawing the letters on the internal Texture. default 24

    *margin*:
      Offsets from the top left corner for the text and space on right
      and bottom. default 5.0

    *justify*:
      L(eft), C(entre), R(ight) default C

    *background_color*:
      filled background in ImageDraw format as above. default None i.e.
      transparent.

    *shader*:
      can be passed to init otherwise needs to be set in set_shader or
      draw. default None

    *f_type*:
      filter type. BUMP will generate a normal map (indented), EMBOSS,
      CONTOUR, BLUR and SMOOTH do what they sound like they will do.
    """
        super(FixedOutlineString, self).__init__(font, mipmap=mipmap)
        self.font = font
        try:
            imgfont = ImageFont.truetype(font, font_size)
        except IOError:
            abspath = os.path.abspath(font)
            msg = "Couldn't find font file '%s'" % font
            if font != abspath:
                msg = "%s - absolute path is '%s'" % (msg, abspath)
            raise Exception(msg)

        justify = justify.upper()
        f_type = f_type.upper()
        ascent, descent = imgfont.getmetrics()
        height = ascent + descent
        lines = string.split('\n')
        nlines = len(lines)
        maxwid = 0
        for l in lines:
            line_wid = imgfont.getsize(l)[0]
            if line_wid > maxwid:
                maxwid = line_wid
        maxwid += 2.0 * margin
        texture_wid = WIDTHS[0]
        for l in WIDTHS:
            if l > maxwid:
                texture_wid = l
                break
            texture_wid = l
        texture_hgt = int(nlines * height + 2 * margin)

        self.im = Image.new("RGBA", (texture_wid, texture_hgt),
                            background_color)
        self.alpha = True
        self.ix, self.iy = texture_wid, texture_hgt
        draw = ImageDraw.Draw(self.im)
        for i, line in enumerate(lines):
            line_len = imgfont.getsize(line)[0]
            if justify == "C":
                xoff = (maxwid - line_len) / 2.0
            elif justify == "L":
                xoff = margin
            else:
                xoff = maxwid - line_len

            # draw the outline by drawing the displaced text repeatedly
            if outline_size > 0:
                for xi in range(-outline_size, outline_size + 1):
                    for yi in range(-outline_size, outline_size + 1):
                        if xi != 0 and yi != 0:
                            draw.text((xoff + xi, margin + i * height + yi),
                                      line,
                                      font=imgfont,
                                      fill=outline)

            draw.text((xoff, margin + i * height),
                      line,
                      font=imgfont,
                      fill=color)

        if f_type == '':
            pass
        elif f_type == 'BUMP':
            self.im = self.make_bump_map()
        else:
            from PIL import ImageFilter
            if f_type == 'EMBOSS':
                self.im = self.im.filter(ImageFilter.EMBOSS)
            elif f_type == 'CONTOUR':
                self.im = self.im.filter(ImageFilter.CONTOUR)
            elif f_type == 'BLUR':
                self.im = self.im.filter(ImageFilter.BLUR)
            elif f_type == 'SMOOTH':
                self.im = self.im.filter(ImageFilter.SMOOTH_MORE)

        #self.image = self.im.convert('RGBA').tostring('raw', 'RGBA')
        self.im = self.im.convert('RGBA')
        self.image = np.array(self.im)
        self._tex = ctypes.c_int()

        bmedge = nlines * height + 2.0 * margin
        self.sprite = Sprite(camera=camera, w=maxwid, h=bmedge)
        buf = self.sprite.buf[0]  #convenience alias
        buf.textures = [self]
        if shader != None:
            self.sprite.shader = shader
            buf.shader = shader
        buf.unib[6] = float(maxwid / texture_wid)  #scale to fit
        buf.unib[7] = float(bmedge / texture_hgt)

    def set_shader(self, shader):
        ''' wrapper for Shape.set_shader'''
        self.sprite.set_shader(shader)

    def draw(self,
             shader=None,
             txtrs=None,
             ntl=None,
             shny=None,
             camera=None,
             mlist=[]):
        '''wrapper for Shape.draw()'''
        self.sprite.draw(shader, txtrs, ntl, shny, camera, mlist)

    def make_bump_map(self):
        """ essentially just blurs the image then allocates R or G values
    according to the rate of change of grayscale in x and y directions
    """
        import numpy as np
        a = np.array(self.im, dtype=np.uint8)
        a = np.average(a, axis=2, weights=[1.0, 1.0, 1.0, 0.0]).astype(int)
        b = [[0.01, 0.025, 0.05, 0.025,
              0.01], [0.025, 0.05, 0.065, 0.05, 0.025],
             [0.05, 0.065, 0.1, 0.065,
              0.05], [0.025, 0.05, 0.065, 0.05, 0.025],
             [0.01, 0.025, 0.05, 0.025, 0.01]]
        c = np.zeros(a.shape, dtype=np.uint8)
        steps = [i - 2 for i in range(5)]
        for i, istep in enumerate(steps):
            for j, jstep in enumerate(steps):
                c += (np.roll(np.roll(a, istep, 0), jstep, 1) *
                      b[i][j]).astype(np.uint8)
        cx = np.roll(c, 1, 0)
        cy = np.roll(c, 1, 1)
        d = np.zeros((a.shape[0], a.shape[1], 4), dtype=np.uint8)
        d[:, :, 0] = ((c - cy) + 127).astype(int)
        d[:, :, 1] = ((c - cx) + 127).astype(int)
        d[:, :, 2] = (np.clip(
            (65025 - (127 - d[:, :, 0])**2 - (127 - d[:, :, 1])**2)**0.5, 0,
            255)).astype(int)
        d[:, :, 3] = 255
        return Image.fromarray(d)

    def _load_disk(self):
        """
Esempio n. 3
0
class FixedOutlineString(Texture):
  """
  A texture containing a simple string drawn using ImageDraw.

  The advantage over a standard String is that it only requires a simple
  Sprite shape for drawing so the gpu has to only draw two triangles
  rather than two triangles for each letter."""

  def __init__(self, font, string, camera=None, color=(255,255,255,255),
               outline=(0,0,0,255), outline_size=0,
               font_size=24, margin=5.0, justify='C',
               background_color=None, shader=None, f_type='', mipmap=True):
    """Arguments:

    *font*:
      File path/name to a TrueType font file.

    *string*:
      String to write.

    *camera*:
      Camera object passed on to constructor of sprite

    *color*:
      Color in format #RRGGBB, (255,0,0,255) etc (as accepted by PIL.ImageDraw)
      default (255, 255, 255, 255) i.e. white 100% alpha

    *font_size*:
      Point size for drawing the letters on the internal Texture. default 24

    *margin*:
      Offsets from the top left corner for the text and space on right
      and bottom. default 5.0

    *justify*:
      L(eft), C(entre), R(ight) default C

    *background_color*:
      filled background in ImageDraw format as above. default None i.e.
      transparent.

    *shader*:
      can be passed to init otherwise needs to be set in set_shader or
      draw. default None

    *f_type*:
      filter type. BUMP will generate a normal map (indented), EMBOSS,
      CONTOUR, BLUR and SMOOTH do what they sound like they will do.
    """
    super(FixedOutlineString, self).__init__(font, mipmap=mipmap)
    self.font = font
    try:
      imgfont = ImageFont.truetype(font, font_size)
    except IOError:
      abspath = os.path.abspath(font)
      msg = "Couldn't find font file '%s'" % font
      if font != abspath:
        msg = "%s - absolute path is '%s'" % (msg, abspath)
      raise Exception(msg)

    justify = justify.upper()
    f_type = f_type.upper()
    ascent, descent = imgfont.getmetrics()
    height = ascent + descent
    lines = string.split('\n')
    nlines = len(lines)
    maxwid = 0
    for l in lines:
      line_wid = imgfont.getsize(l)[0]
      if line_wid > maxwid:
        maxwid = line_wid
    maxwid += 2.0 * margin
    texture_wid = WIDTHS[0]
    for l in WIDTHS:
      if l > maxwid:
        texture_wid = l
        break
      texture_wid = l
    texture_hgt = int(nlines * height + 2 * margin)

    self.im = Image.new("RGBA", (texture_wid, texture_hgt), background_color)
    self.alpha = True
    self.ix, self.iy = texture_wid, texture_hgt
    draw = ImageDraw.Draw(self.im)
    for i, line in enumerate(lines):
      line_len = imgfont.getsize(line)[0]
      if justify == "C":
        xoff = (maxwid - line_len) / 2.0
      elif justify == "L":
        xoff = margin
      else:
        xoff = maxwid - line_len

      # draw the outline by drawing the displaced text repeatedly
      if outline_size > 0:
          for xi in range(-outline_size, outline_size + 1):
              for yi in range(-outline_size, outline_size + 1):
                  if xi != 0 and yi != 0:
                      draw.text((xoff + xi, margin + i * height + yi), line, font=imgfont, fill=outline)

      draw.text((xoff, margin + i * height), line, font=imgfont, fill=color)

    if f_type == '':
      pass
    elif f_type == 'BUMP':
      self.im = self.make_bump_map()
    else:
      from PIL import ImageFilter
      if f_type == 'EMBOSS':
        self.im = self.im.filter(ImageFilter.EMBOSS)
      elif f_type == 'CONTOUR':
        self.im = self.im.filter(ImageFilter.CONTOUR)
      elif f_type == 'BLUR':
        self.im = self.im.filter(ImageFilter.BLUR)
      elif f_type == 'SMOOTH':
        self.im = self.im.filter(ImageFilter.SMOOTH_MORE)

    #self.image = self.im.convert('RGBA').tostring('raw', 'RGBA')
    self.im = self.im.convert('RGBA')
    self.image = np.array(self.im)
    self._tex = ctypes.c_int()

    bmedge = nlines * height + 2.0 * margin
    self.sprite = Sprite(camera=camera, w=maxwid, h=bmedge)
    buf = self.sprite.buf[0] #convenience alias
    buf.textures = [self]
    if shader != None:
      self.sprite.shader = shader
      buf.shader = shader
    buf.unib[6] = float(maxwid / texture_wid) #scale to fit
    buf.unib[7] = float(bmedge / texture_hgt)

  def set_shader(self, shader):
    ''' wrapper for Shape.set_shader'''
    self.sprite.set_shader(shader)

  def draw(self, shader=None, txtrs=None, ntl=None, shny=None, camera=None, mlist=[]):
    '''wrapper for Shape.draw()'''
    self.sprite.draw(shader, txtrs, ntl, shny, camera, mlist)

  def make_bump_map(self):
    """ essentially just blurs the image then allocates R or G values
    according to the rate of change of grayscale in x and y directions
    """
    import numpy as np
    a = np.array(self.im, dtype=np.uint8)
    a = np.average(a, axis=2, weights=[1.0, 1.0, 1.0, 0.0]).astype(int)
    b = [[0.01, 0.025, 0.05, 0.025, 0.01],
         [0.025,0.05,  0.065,0.05,  0.025],
         [0.05, 0.065, 0.1,  0.065, 0.05],
         [0.025,0.05,  0.065,0.05,  0.025],
         [0.01, 0.025, 0.05, 0.025, 0.01]]
    c = np.zeros(a.shape, dtype=np.uint8)
    steps = [i - 2 for i in range(5)]
    for i, istep in enumerate(steps):
      for j, jstep in enumerate(steps):
        c += (np.roll(np.roll(a, istep, 0), jstep, 1) * b[i][j]).astype(np.uint8)
    cx = np.roll(c, 1, 0)
    cy = np.roll(c, 1, 1)
    d = np.zeros((a.shape[0], a.shape[1], 4), dtype=np.uint8)
    d[:, :, 0] = ((c - cy) + 127).astype(int)
    d[:, :, 1] = ((c - cx) + 127).astype(int)
    d[:, :, 2] = (np.clip((65025 - (127 - d[:, :, 0]) ** 2 - (127 - d[:, :, 1]) ** 2) ** 0.5, 0, 255)).astype(int)
    d[:, :, 3] = 255
    return Image.fromarray(d)

  def _load_disk(self):
    """
Esempio n. 4
0
class FixedString(Texture):
    """
  A texture containing a simple string drawn using ImageDraw.
  
  The advantage over a standard String is that it only requires a simple
  Sprite shape for drawing so the gpu has to only draw two triangles
  rather than two triangles for each letter."""

    def __init__(
        self,
        font,
        string,
        camera=None,
        color=(255, 255, 255, 255),
        font_size=24,
        margin=5.0,
        justify="C",
        background_color=None,
        shader=None,
        f_type="",
        mipmap=True,
    ):
        """Arguments:
    
    *font*:
      File path/name to a TrueType font file.

    *string*:
      String to write.
      
    *camera*:
      Camera object passed on to constructor of sprite
      
    *color*:
      Color in format '#RRGGBB', (255,0,0,255), 'orange' etc (as accepted 
      by PIL.ImageDraw) default (255, 255, 255, 255) i.e. white 100% alpha

    *font_size*:
      Point size for drawing the letters on the internal Texture. default 24

    *margin*:
      Offsets from the top left corner for the text and space on right
      and bottom. default 5.0
      
    *justify*:
      L(eft), C(entre), R(ight) default C

    *background_color*:
      filled background in ImageDraw format as above. default None i.e.
      transparent. 
      
    *shader*:
      can be passed to init otherwise needs to be set in set_shader or
      draw. default None
      
    *f_type*:
      filter type. BUMP will generate a normal map (indented by default,
      +BUMP or BUMP+ will make it stick out), EMBOSS, CONTOUR, BLUR and 
      SMOOTH do what they sound like they will do.
    """
        super(FixedString, self).__init__(font, mipmap=mipmap)
        self.font = font
        try:
            imgfont = ImageFont.truetype(font, font_size)
        except IOError:
            abspath = os.path.abspath(font)
            msg = "Couldn't find font file '%s'" % font
            if font != abspath:
                msg = "%s - absolute path is '%s'" % (msg, abspath)
            raise Exception(msg)

        justify = justify.upper()
        f_type = f_type.upper()
        ascent, descent = imgfont.getmetrics()
        height = ascent + descent
        lines = string.split("\n")
        nlines = len(lines)
        maxwid = 0
        for l in lines:
            line_wid = imgfont.getsize(l)[0]
            if line_wid > maxwid:
                maxwid = line_wid
        maxwid += 2.0 * margin
        texture_wid = WIDTHS[0]
        for l in WIDTHS:
            if l > maxwid:
                texture_wid = l
                break
            texture_wid = l
        texture_hgt = int(nlines * height + 2 * margin)

        self.im = Image.new("RGBA", (texture_wid, texture_hgt), background_color)
        self.ix, self.iy = texture_wid, texture_hgt
        draw = ImageDraw.Draw(self.im)
        for i, line in enumerate(lines):
            line_len = imgfont.getsize(line)[0]
            if justify == "C":
                xoff = (maxwid - line_len) / 2.0
            elif justify == "L":
                xoff = margin
            else:
                xoff = maxwid - line_len
            draw.text((xoff, margin + i * height), line, font=imgfont, fill=color)

        if f_type == "":
            self.image = np.array(self.im)
        elif "BUMP" in f_type:
            amount = -1.0 if "+" in f_type else 1.0
            self.image = self._normal_map(np.array(self.im, dtype=np.uint8), amount)
        else:
            from PIL import ImageFilter

            if f_type == "EMBOSS":
                self.im = self.im.filter(ImageFilter.EMBOSS)
            elif f_type == "CONTOUR":
                self.im = self.im.filter(ImageFilter.CONTOUR)
            elif f_type == "BLUR":
                self.im = self.im.filter(ImageFilter.BLUR)
            elif f_type == "SMOOTH":
                self.im = self.im.filter(ImageFilter.SMOOTH_MORE)
            self.image = np.array(self.im)
            if background_color is None:
                if isinstance(color, str):
                    from PIL import ImageColor

                    color = ImageColor.getrgb(color)
                self.image[:, :, :3] = color[:3]
        self._tex = ctypes.c_uint()

        bmedge = nlines * height + 2.0 * margin
        self.sprite = Sprite(camera=camera, w=maxwid, h=bmedge)
        buf = self.sprite.buf[0]  # convenience alias
        buf.textures = [self]
        if shader != None:
            self.sprite.shader = shader
            buf.shader = shader
        buf.unib[6] = float(maxwid / texture_wid)  # scale to fit
        buf.unib[7] = float(bmedge / texture_hgt)

    def set_shader(self, shader):
        """ wrapper for Shape.set_shader"""
        self.sprite.set_shader(shader)

    def draw(self, shader=None, txtrs=None, ntl=None, shny=None, camera=None, mlist=[]):
        """wrapper for Shape.draw()"""
        self.sprite.draw(shader, txtrs, ntl, shny, camera, mlist)

    def _load_disk(self):
        """
Esempio n. 5
0
class FixedString(Texture):
  """
  A texture containing a simple string drawn using ImageDraw.
  
  The advantage over a standard String is that it only requires a simple
  Sprite shape for drawing so the gpu has to only draw two triangles
  rather than two triangles for each letter."""

  def __init__(self, font, string, camera=None, color=(255,255,255,255),
               shadow=(0,0,0,255), shadow_radius=0,
               font_size=24, margin=5.0, justify='C',
               background_color=None, shader=None, f_type='', mipmap=True):
    """Arguments:
    
    *font*:
      File path/name to a TrueType font file.

    *string*:
      String to write.
      
    *camera*:
      Camera object passed on to constructor of sprite
      
    *color*:
      Color in format '#RRGGBB', (255,0,0,255), 'orange' etc (as accepted 
      by PIL.ImageDraw) default (255, 255, 255, 255) i.e. white 100% alpha

    *shadow*:
      Color of shadow, default black.

    *shadow_radius*:
      Gaussian blur radius applied to shadow layer, default 0 (no shadow)

    *font_size*:
      Point size for drawing the letters on the internal Texture. default 24

    *margin*:
      Offsets from the top left corner for the text and space on right
      and bottom. default 5.0
      
    *justify*:
      L(eft), C(entre), R(ight) default C

    *background_color*:
      filled background in ImageDraw format as above. default None i.e.
      transparent. 
      
    *shader*:
      can be passed to init otherwise needs to be set in set_shader or
      draw. default None
      
    *f_type*:
      filter type. BUMP will generate a normal map (indented by default,
      +BUMP or BUMP+ will make it stick out), EMBOSS, CONTOUR, BLUR and 
      SMOOTH do what they sound like they will do.
    """
    super(FixedString, self).__init__(font, mipmap=mipmap)
    self.font = font
    try:
      imgfont = ImageFont.truetype(font, font_size)
    except IOError:
      abspath = os.path.abspath(font)
      msg = "Couldn't find font file '%s'" % font
      if font != abspath:
        msg = "%s - absolute path is '%s'" % (msg, abspath)
      raise Exception(msg)

    justify = justify.upper()
    f_type = f_type.upper()
    ascent, descent = imgfont.getmetrics()
    height = ascent + descent
    lines = string.split('\n')
    nlines = len(lines)
    maxwid = 0
    for l in lines:
      line_wid = imgfont.getsize(l)[0]
      if line_wid > maxwid:
        maxwid = line_wid
    maxwid += 2.0 * margin
    texture_wid = WIDTHS[0]
    for l in WIDTHS:
      if l > maxwid:
        texture_wid = l
        break
      texture_wid = l
    texture_hgt = int(nlines * height + 2 * margin)
    
    self.im = Image.new("RGBA", (texture_wid, texture_hgt), background_color)
    self.ix, self.iy = texture_wid, texture_hgt
    draw = ImageDraw.Draw(self.im)
    if shadow_radius > 0:
      from PIL import ImageFilter
      self._render_text(lines, justify, margin, imgfont, maxwid, height, shadow, draw)
      self.im = self.im.filter(ImageFilter.GaussianBlur(radius=shadow_radius))
      if background_color == None:
        im_arr = self._force_color(np.array(self.im), shadow)
        try: # numpy not quite working fully in pypy so have to convert tobytes
          self.im = Image.fromarray(im_arr)
        except:
          h, w, c = im_arr.shape
          rgb = 'RGB' if c == 3 else 'RGBA'
          self.im = Image.frombytes(rgb, (w, h), im_arr.tobytes())
        
      draw = ImageDraw.Draw(self.im)

    self._render_text(lines, justify, margin, imgfont, maxwid, height, color, draw)

    force_color = background_color is None and shadow_radius == 0
    if f_type == '':
      self.image = np.array(self.im)
    elif 'BUMP' in f_type:
      amount = -1.0 if '+' in f_type else 1.0
      self.image = self._normal_map(np.array(self.im, dtype=np.uint8), amount)
      force_color = False

    else:
      from PIL import ImageFilter
      if f_type == 'EMBOSS':
        self.im = self.im.filter(ImageFilter.EMBOSS)
      elif f_type == 'CONTOUR':
        self.im = self.im.filter(ImageFilter.CONTOUR)
      elif f_type == 'BLUR':
        self.im = self.im.filter(ImageFilter.BLUR)
      elif f_type == 'SMOOTH':
        self.im = self.im.filter(ImageFilter.SMOOTH_MORE)
      self.image = np.array(self.im)

    if force_color:
      self.image = self._force_color(self.image, color)

    self._tex = ctypes.c_uint()
    
    bmedge = nlines * height + 2.0 * margin
    self.sprite = Sprite(camera=camera, w=maxwid, h=bmedge)
    buf = self.sprite.buf[0] #convenience alias
    buf.textures = [self]
    if shader != None:
      self.sprite.shader = shader
      buf.shader = shader
    buf.unib[6] = float(maxwid / texture_wid) #scale to fit
    buf.unib[7] = float(bmedge / texture_hgt)

  def _render_text(self, lines, justify, margin, imgfont, maxwid, height, color, draw):
    for i, line in enumerate(lines):
      line_len = imgfont.getsize(line)[0] 
      if justify == "C":
        xoff = (maxwid - line_len) / 2.0
      elif justify == "L":
        xoff = margin
      else:
        xoff = maxwid - line_len

      draw.text((xoff, margin + i * height), line, font=imgfont, fill=color)

    
  def set_shader(self, shader):
    ''' wrapper for Shape.set_shader'''
    self.sprite.set_shader(shader)
    
  def draw(self, shader=None, txtrs=None, ntl=None, shny=None, camera=None):
    '''wrapper for Shape.draw()'''
    self.sprite.draw(shader, txtrs, ntl, shny, camera)
    
  def _force_color(self, im_array, color):
    """
    Overwrite color of all pixels as PIL renders text incorrectly when drawing on transparent backgrounds
    http://nedbatchelder.com/blog/200801/truly_transparent_text_with_pil.html
    """
    if isinstance(color, str):
      from PIL import ImageColor
      color = ImageColor.getrgb(color)
    im_array[:,:,:3] = color[:3]
    return im_array

  def _load_disk(self):
    """