--- /dev/null
+#!/usr/bin/env python\r
+# -*- coding: utf-8\r
+#\r
+# Provides some text display functions for wx + ogl\r
+# Copyright (C) 2007 Christian Brugger, Stefan Hacker\r
+#\r
+# This program is free software; you can redistribute it and/or modify\r
+# it under the terms of the GNU General Public License as published by\r
+# the Free Software Foundation; either version 2 of the License, or\r
+# (at your option) any later version.\r
+#\r
+# This program is distributed in the hope that it will be useful,\r
+# but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+# GNU General Public License for more details.\r
+#\r
+# You should have received a copy of the GNU General Public License along\r
+# with this program; if not, write to the Free Software Foundation, Inc.,\r
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+\r
+import wx\r
+from OpenGL.GL import *\r
+\r
+"""\r
+Optimize with psyco if possible, this gains us about 50% speed when\r
+creating our textures in trade for about 4MBytes of additional memory usage for\r
+psyco. If you don't like loosing the memory you have to turn the lines following\r
+"enable psyco" into a comment while uncommenting the line after "Disable psyco".\r
+"""\r
+#Try to enable psyco\r
+try:\r
+ import psyco\r
+ psyco_optimized = False\r
+except ImportError:\r
+ psyco = None\r
+ \r
+#Disable psyco\r
+#psyco = None\r
+ \r
+class TextElement(object):\r
+ """\r
+ A simple class for using system Fonts to display\r
+ text in an OpenGL scene\r
+ """\r
+ def __init__(self,\r
+ text = '',\r
+ font = None,\r
+ foreground = wx.BLACK,\r
+ centered = False):\r
+ """\r
+ text (String) - Text\r
+ font (wx.Font) - Font to draw with (None = System default)\r
+ foreground (wx.Color) - Color of the text\r
+ or (wx.Bitmap)- Bitmap to overlay the text with\r
+ centered (bool) - Center the text\r
+ \r
+ Initializes the TextElement\r
+ """\r
+ # save given variables\r
+ self._text = text\r
+ self._lines = text.split('\n')\r
+ self._font = font\r
+ self._foreground = foreground\r
+ self._centered = centered\r
+ \r
+ # init own variables\r
+ self._owner_cnt = 0 #refcounter\r
+ self._texture = None #OpenGL texture ID\r
+ self._text_size = None #x/y size tuple of the text\r
+ self._texture_size= None #x/y Texture size tuple\r
+ \r
+ # create Texture\r
+ self.createTexture()\r
+ \r
+\r
+ #---Internal helpers\r
+ \r
+ def _getUpper2Base(self, value):\r
+ """\r
+ Returns the lowest value with the power of\r
+ 2 greater than 'value' (2^n>value)\r
+ """\r
+ base2 = 1\r
+ while base2 < value:\r
+ base2 *= 2\r
+ return base2\r
+ \r
+ #---Functions\r
+ \r
+ def draw_text(self, position = wx.Point(0,0), scale = 1.0, rotation = 0):\r
+ """\r
+ position (wx.Point) - x/y Position to draw in scene\r
+ scale (float) - Scale\r
+ rotation (int) - Rotation in degree\r
+ \r
+ Draws the text to the scene\r
+ """\r
+ #Enable necessary functions\r
+ glColor(1,1,1,1)\r
+ glEnable(GL_TEXTURE_2D) \r
+ glEnable(GL_ALPHA_TEST) #Enable alpha test\r
+ glAlphaFunc(GL_GREATER, 0)\r
+ glEnable(GL_BLEND) #Enable blending\r
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)\r
+ #Bind texture\r
+ glBindTexture(GL_TEXTURE_2D, self._texture)\r
+ \r
+ ow, oh = self._text_size\r
+ w , h = self._texture_size\r
+ #Perform transformations\r
+ glPushMatrix()\r
+ glTranslated(position.x, position.y, 0)\r
+ glRotate(-rotation, 0, 0, 1)\r
+ glScaled(scale, scale, scale)\r
+ if self._centered:\r
+ glTranslate(-w/2, -oh/2, 0)\r
+ #Draw vertices\r
+ glBegin(GL_QUADS)\r
+ glTexCoord2f(0,0); glVertex2f(0,0)\r
+ glTexCoord2f(0,1); glVertex2f(0,h)\r
+ glTexCoord2f(1,1); glVertex2f(w,h)\r
+ glTexCoord2f(1,0); glVertex2f(w,0)\r
+ glEnd()\r
+ glPopMatrix()\r
+ \r
+ #Disable features\r
+ glDisable(GL_BLEND)\r
+ glDisable(GL_ALPHA_TEST)\r
+ glDisable(GL_TEXTURE_2D)\r
+ \r
+ def createTexture(self):\r
+ """\r
+ Creates a texture from the settings saved in TextElement, to be able to use normal\r
+ system fonts conviently a wx.MemoryDC is used to draw on a wx.Bitmap. As wxwidgets \r
+ device contexts don't support alpha at all it is necessary to apply a little hack\r
+ to preserve antialiasing without sticking to a fixed background color:\r
+ \r
+ We draw the bmp in b/w mode so we can use its data as a alpha channel for a solid\r
+ color bitmap which after GL_ALPHA_TEST and GL_BLEND will show a nicely antialiased\r
+ text on any surface.\r
+ \r
+ To access the raw pixel data the bmp gets converted to a wx.Image. Now we just have\r
+ to merge our foreground color with the alpha data we just created and push it all\r
+ into a OpenGL texture and we are DONE *inhalesdelpy*\r
+ \r
+ DRAWBACK of the whole conversion thing is a really long time for creating the\r
+ texture. If you see any optimizations that could save time PLEASE CREATE A PATCH!!!\r
+ """\r
+ # get a memory dc\r
+ dc = wx.MemoryDC()\r
+ \r
+ # set our font\r
+ dc.SetFont(self._font)\r
+ \r
+ # Approximate extend to next power of 2 and create our bitmap\r
+ # REMARK: You wouldn't believe how much fucking speed this little\r
+ # sucker gains compared to sizes not of the power of 2. It's like\r
+ # 500ms --> 0.5ms (on my ATI-GPU powered Notebook). On Sams nvidia\r
+ # machine there don't seem to occur any losses...bad drivers?\r
+ ow, oh = dc.GetMultiLineTextExtent(self._text)[:2]\r
+ w, h = self._getUpper2Base(ow), self._getUpper2Base(oh)\r
+ \r
+ self._text_size = wx.Size(ow,oh)\r
+ self._texture_size = wx.Size(w,h)\r
+ bmp = wx.EmptyBitmap(w,h)\r
+ \r
+ \r
+ #Draw in b/w mode to bmp so we can use it as alpha channel\r
+ dc.SelectObject(bmp)\r
+ dc.SetBackground(wx.BLACK_BRUSH)\r
+ dc.Clear()\r
+ dc.SetTextForeground(wx.WHITE)\r
+ x,y = 0,0\r
+ centered = self.centered\r
+ for line in self._lines:\r
+ if not line: line = ' '\r
+ tw, th = dc.GetTextExtent(line)\r
+ if centered:\r
+ x = int(round((w-tw)/2))\r
+ dc.DrawText(line, x, y)\r
+ x = 0\r
+ y += th\r
+ #Release the dc\r
+ dc.SelectObject(wx.NullBitmap)\r
+ del dc\r
+\r
+ #Generate a correct RGBA data string from our bmp \r
+ """\r
+ NOTE: You could also use wx.AlphaPixelData to access the pixel data\r
+ in 'bmp' directly, but the iterator given by it is much slower than\r
+ first converting to an image and using wx.Image.GetData().\r
+ """\r
+ img = wx.ImageFromBitmap(bmp)\r
+ alpha = img.GetData()\r
+ \r
+ if isinstance(self._foreground, wx.Color): \r
+ """\r
+ If we have a static color... \r
+ """ \r
+ r,g,b = self._foreground.Get()\r
+ color = "%c%c%c" % (chr(r), chr(g), chr(b))\r
+ \r
+ data = ''\r
+ for i in xrange(0, len(alpha)-1, 3):\r
+ data += color + alpha[i]\r
+ \r
+ elif isinstance(self._foreground, wx.Bitmap):\r
+ """\r
+ If we have a bitmap...\r
+ """\r
+ bg_img = wx.ImageFromBitmap(self._foreground)\r
+ bg = bg_img.GetData()\r
+ bg_width = self._foreground.GetWidth()\r
+ bg_height = self._foreground.GetHeight()\r
+ \r
+ data = ''\r
+\r
+ for y in xrange(0, h):\r
+ for x in xrange(0, w):\r
+ if (y > (bg_height-1)) or (x > (bg_width-1)): \r
+ color = "%c%c%c" % (chr(0),chr(0),chr(0))\r
+ else:\r
+ pos = (x+y*bg_width) * 3\r
+ color = bg[pos:pos+3]\r
+ data += color + alpha[(x+y*w)*3]\r
+\r
+\r
+ # now convert it to ogl texture\r
+ self._texture = glGenTextures(1)\r
+ glBindTexture(GL_TEXTURE_2D, self._texture)\r
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)\r
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)\r
+ \r
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)\r
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 2)\r
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data)\r
+ \r
+ def deleteTexture(self):\r
+ """\r
+ Deletes the OpenGL texture object\r
+ """\r
+ if self._texture:\r
+ if glIsTexture(self._texture):\r
+ glDeleteTextures(self._texture)\r
+ else:\r
+ self._texture = None\r
+\r
+ def bind(self):\r
+ """\r
+ Increase refcount\r
+ """\r
+ self._owner_cnt += 1\r
+ \r
+ def release(self):\r
+ """\r
+ Decrease refcount\r
+ """\r
+ self._owner_cnt -= 1\r
+ \r
+ def isBound(self):\r
+ """\r
+ Return refcount\r
+ """\r
+ return self._owner_cnt\r
+ \r
+ def __del__(self):\r
+ """\r
+ Destructor\r
+ """\r
+ self.deleteTexture()\r
+\r
+ #---Getters/Setters\r
+ \r
+ def getText(self): return self._text\r
+ def getFont(self): return self._font\r
+ def getForeground(self): return self._foreground\r
+ def getCentered(self): return self._centered\r
+ def getTexture(self): return self._texture\r
+ def getTexture_size(self): return self._texture_size\r
+\r
+ def getOwner_cnt(self): return self._owner_cnt\r
+ def setOwner_cnt(self, value):\r
+ self._owner_cnt = value\r
+ \r
+ #---Properties\r
+ \r
+ text = property(getText, None, None, "Text of the object")\r
+ font = property(getFont, None, None, "Font of the object")\r
+ foreground = property(getForeground, None, None, "Color of the text")\r
+ centered = property(getCentered, None, None, "Is text centered")\r
+ owner_cnt = property(getOwner_cnt, setOwner_cnt, None, "Owner count")\r
+ texture = property(getTexture, None, None, "Used texture")\r
+ texture_size = property(getTexture_size, None, None, "Size of the used texture") \r
+ \r
+\r
+class Text(object):\r
+ """\r
+ A simple class for using System Fonts to display text in\r
+ an OpenGL scene. The Text adds a global Cache of already\r
+ created text elements to TextElement's base functionality\r
+ so you can save some memory and increase speed\r
+ """\r
+ _texts = [] #Global cache for TextElements\r
+ \r
+ def __init__(self,\r
+ text = 'Text',\r
+ font = None,\r
+ font_size = 8,\r
+ foreground = wx.BLACK,\r
+ centered = False,\r
+ bold = False):\r
+ """\r
+ text (string) - displayed text\r
+ font (wx.Font) - if None, system default font will be used with font_size\r
+ font_size (int) - font size in points\r
+ foreground (wx.Color) - Color of the text\r
+ or (wx.Bitmap) - Bitmap to overlay the text with\r
+ centered (bool) - should the text drawn centered towards position?\r
+ \r
+ Initializes the text object\r
+ """\r
+ #Init/save variables\r
+ self._aloc_text = None\r
+ self._text = text\r
+ self._font_size = font_size\r
+ self._foreground= foreground\r
+ self._centered = centered\r
+ \r
+ #Check if we are offered a font\r
+ if not font:\r
+ #if not use the system default\r
+ self._font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)\r
+ else: \r
+ #save it\r
+ self._font = font\r
+ \r
+ if bold: self._font.SetWeight(wx.FONTWEIGHT_BOLD)\r
+ \r
+ #Bind us to our texture\r
+ self._initText()\r
+\r
+ #---Internal helpers\r
+\r
+ def _initText(self):\r
+ """\r
+ Initializes/Reinitializes the Text object by binding it\r
+ to a TextElement suitable for its current settings\r
+ """\r
+ #Check if we already bound to a texture\r
+ if self._aloc_text:\r
+ #if so release it\r
+ self._aloc_text.release()\r
+ if not self._aloc_text.isBound():\r
+ self._texts.remove(self._aloc_text)\r
+ self._aloc_text = None\r
+ \r
+ #Adjust our font\r
+ self._font.SetPointSize(self._font_size)\r
+ \r
+ #Search for existing element in our global buffer\r
+ for element in self._texts:\r
+ if element.text == self._text and\\r
+ element.font == self._font and\\r
+ element.foreground == self._foreground and\\r
+ element.centered == self._centered:\r
+ # We already exist in global buffer ;-)\r
+ element.bind()\r
+ self._aloc_text = element\r
+ break\r
+ \r
+ if not self._aloc_text:\r
+ # We are not in the global buffer, let's create ourselves\r
+ aloc_text = self._aloc_text = TextElement(self._text,\r
+ self._font,\r
+ self._foreground,\r
+ self._centered)\r
+ aloc_text.bind()\r
+ self._texts.append(aloc_text)\r
+ \r
+ def __del__(self):\r
+ """\r
+ Destructor\r
+ """\r
+ aloc_text = self._aloc_text\r
+ aloc_text.release()\r
+ if not aloc_text.isBound():\r
+ self._texts.remove(aloc_text)\r
+ \r
+ #---Functions\r
+ \r
+ def draw_text(self, position = wx.Point(0,0), scale = 1.0, rotation = 0):\r
+ """\r
+ position (wx.Point) - x/y Position to draw in scene\r
+ scale (float) - Scale\r
+ rotation (int) - Rotation in degree\r
+ \r
+ Draws the text to the scene\r
+ """\r
+ \r
+ self._aloc_text.draw_text(position, scale, rotation)\r
+\r
+ #---Setter/Getter\r
+ \r
+ def getText(self): return self._text\r
+ def setText(self, value, reinit = True):\r
+ """\r
+ value (bool) - New Text\r
+ reinit (bool) - Create a new texture\r
+ \r
+ Sets a new text\r
+ """\r
+ self._text = value\r
+ if reinit:\r
+ self._initText()\r
+\r
+ def getFont(self): return self._font\r
+ def setFont(self, value, reinit = True):\r
+ """\r
+ value (bool) - New Font\r
+ reinit (bool) - Create a new texture\r
+ \r
+ Sets a new font\r
+ """\r
+ self._font = value\r
+ if reinit:\r
+ self._initText()\r
+\r
+ def getFont_size(self): return self._font_size\r
+ def setFont_size(self, value, reinit = True):\r
+ """\r
+ value (bool) - New font size\r
+ reinit (bool) - Create a new texture\r
+ \r
+ Sets a new font size\r
+ """\r
+ self._font_size = value\r
+ if reinit:\r
+ self._initText()\r
+\r
+ def getForeground(self): return self._foreground\r
+ def setForeground(self, value, reinit = True):\r
+ """\r
+ value (bool) - New centered value\r
+ reinit (bool) - Create a new texture\r
+ \r
+ Sets a new value for 'centered'\r
+ """\r
+ self._foreground = value\r
+ if reinit:\r
+ self._initText()\r
+\r
+ def getCentered(self): return self._centered\r
+ def setCentered(self, value, reinit = True):\r
+ """\r
+ value (bool) - New centered value\r
+ reinit (bool) - Create a new texture\r
+ \r
+ Sets a new value for 'centered'\r
+ """\r
+ self._centered = value\r
+ if reinit:\r
+ self._initText()\r
+ \r
+ def get_size(self):\r
+ """\r
+ Returns a text size tuple\r
+ """\r
+ return self._aloc_text._text_size\r
+ \r
+ def getTexture_size(self):\r
+ """\r
+ Returns a texture size tuple\r
+ """\r
+ return self._aloc_text.texture_size\r
+ \r
+ def getTextElement(self):\r
+ """\r
+ Returns the text element bound to the Text class\r
+ """\r
+ return self._aloc_text\r
+ \r
+ def getTexture(self):\r
+ """\r
+ Returns the texture of the bound TextElement\r
+ """\r
+ return self._aloc_text.texture\r
+\r
+ \r
+ #---Properties\r
+ \r
+ text = property(getText, setText, None, "Text of the object")\r
+ font = property(getFont, setFont, None, "Font of the object")\r
+ font_size = property(getFont_size, setFont_size, None, "Font size")\r
+ foreground = property(getForeground, setForeground, None, "Color/Overlay bitmap of the text")\r
+ centered = property(getCentered, setCentered, None, "Display the text centered")\r
+ texture_size = property(getTexture_size, None, None, "Size of the used texture")\r
+ texture = property(getTexture, None, None, "Texture of bound TextElement")\r
+ text_element = property(getTextElement,None , None, "TextElement bound to this class")\r
+\r
+#Optimize critical functions\r
+if psyco and not psyco_optimized:\r
+ psyco.bind(TextElement.createTexture)\r
+ psyco_optimized = True\r