]> Git trees. - libqmvoc.git/commitdiff
main change: added TextRenderer class (and CharRenderer helper class)
authorBenoît Jacob <jacob.benoit.1@gmail.com>
Sun, 2 Jul 2006 11:35:46 +0000 (11:35 +0000)
committerBenoît Jacob <jacob.benoit.1@gmail.com>
Sun, 2 Jul 2006 11:35:46 +0000 (11:35 +0000)
TextRenderer comment (sorry if this sounds too verbose, I just though it'd be
useful to explain why I didn't just use QGLWidget::renderText()) :

/** This class renders text inside a QGLWidget. It replaces the functionality
* of QGLWidget::renderText(). The advantages over renderText() include:
*  - supports any font, any character encoding supported by Qt
*    (renderText is 8-bit-only and can only use "OpenGL-compatible" fonts)
*  - does not use any library outside Qt (renderText uses FreeType on X11)
*  - renders characters as textured quads instead of calling glDrawPixels,
*    which does not make much of a difference on MesaGL, but can be a lot
*    faster and safer with other (buggy) OpenGL implementations. It will also
*    allow to add more graphical effects in the future, like rotation,
*    if we ever need that.
*  - the characters are stored as 8bpp Alpha, which takes 4 times less
*    memory than the 32bpp RGBA used by renderText.
*  - the characters are rendered on-the-fly on the first time they appear
*    in a QString being printed. This is achieved using a QHash to test whether
*    a character has already been rendered.

M    src/kalziumglwidget.h
M    src/kalziumglhelperclasses.h
M    src/moleculeview.cpp
M    src/kalziumglwidget.cpp
M    src/kalziumglhelperclasses.cpp

svn path=/trunk/KDE/kdeedu/kalzium/src/kalziumglwidget.h; revision=557087

kalzium/kalziumglhelperclasses.cpp
kalzium/kalziumglhelperclasses.h
kalzium/kalziumglwidget.cpp
kalzium/kalziumglwidget.h

index 5756a70f18ebd7d3e56335ba505fb5027f1b30e3..ba7a135bba7c03ab6533d9a4af128c9c880ef34e 100644 (file)
@@ -269,12 +269,12 @@ void Sphere::drawScaled( GLfloat radius )
        }
 
        GLfloat factor = radius / m_radius;
-       glEnable( GL_NORMALIZE );
+//     glEnable( GL_NORMALIZE );
        glPushMatrix();
        glScalef( factor, factor, factor );
        draw();
        glPopMatrix();
-       glDisable( GL_NORMALIZE );
+//     glDisable( GL_NORMALIZE );
 }
 
 Cylinder::Cylinder()
@@ -328,93 +328,182 @@ void Cylinder::initialize()
        m_isInitialized = true;
 }
 
-TextPainter::TextPainter()
+CharRenderer::CharRenderer()
 {
-       m_width = 0;
-       m_height = 0;
-       m_image = 0;
-       m_painter = 0;
-       m_fontMetrics = 0;
+       m_texture = 0;
+       m_displayList = 0;
 }
 
-TextPainter::~TextPainter()
+CharRenderer::~CharRenderer()
 {
-       if( m_image ) delete m_image;
-       if( m_painter ) delete m_painter;
-       if( m_fontMetrics ) delete m_fontMetrics;
+       if( m_texture ) glDeleteTextures( 1, &m_texture );
+       if( m_displayList ) glDeleteLists( m_displayList, 1 );
 }
-
-bool TextPainter::print( QGLWidget *glwidget, int x, int y, const QString &string)
+bool CharRenderer::initialize( QChar c, const QFont &font )
 {
-       glDisable( GL_LIGHTING );
-       glEnable(GL_TEXTURE_2D);
-       glEnable( GL_BLEND );
-
-       if( ! m_painter )
-       {
-               m_painter = new QPainter();
-               if( ! m_painter ) return false;
-       }
-
-       if( ! m_fontMetrics )
-       {
-               
-               m_fontMetrics = new QFontMetrics(glwidget->font());
-               if( ! m_fontMetrics ) return false;
-       }
-
-       int new_width = m_fontMetrics->width( string );
-       int new_height = m_fontMetrics->height();
+       if( m_displayList ) return true;
+       
+       QFontMetrics fontMetrics ( font );
+       m_width = fontMetrics.width( c );
+       m_height = fontMetrics.height();
+       if( m_width == 0 || m_height == 0 ) return false;
+       QImage image( m_width, m_height, QImage::Format_RGB32 );
        
-       if(new_width == 0 || new_height == 0)
+       QPainter painter;
+       painter.begin( &image );
+       painter.setFont( font );
+       painter.setRenderHint( QPainter::TextAntialiasing );
+       painter.setBackground(Qt::black);
+       painter.eraseRect( image.rect() );
+       painter.setPen(Qt::white);
+       painter.drawText ( 0, 0, m_width, m_height, Qt::AlignBottom, c);
+       painter.end();
+
+       GLubyte *bitmap = new GLubyte [m_width * m_height];
+       if( bitmap == 0 ) return false;
+
+       for( int i = 0; i < m_width; i++)
+       for( int j = 0; j < m_height; j++)
        {
-               return false;
+               bitmap[ i + m_width * j ] =
+                       image.pixel( i, m_height - j - 1 ) & 0x000000ff;
        }
 
-       if( new_width > m_width || new_height > m_height )
-       {
-               if( m_image ) delete m_image;
-               m_width = ( new_width > m_width ) ? new_width : m_width;
-               m_height = ( new_height > m_height ) ? new_height : m_height;
-               m_image = new QImage( m_width, m_height, QImage::Format_ARGB32 );
-       }
+       glGenTextures( 1, &m_texture );
+       if( m_texture == 0 ) return false;
+
+       glBindTexture(GL_TEXTURE_2D,m_texture) ;
+       glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+       glTexImage2D (
+               GL_TEXTURE_2D,
+               0,
+               GL_ALPHA,
+               m_width,
+               m_height,
+               0,
+               GL_ALPHA,
+               GL_UNSIGNED_BYTE,
+               bitmap );
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 
-       m_painter->begin( m_image );
-       m_painter->setFont(glwidget->font());
-       m_painter->setRenderHint(QPainter::TextAntialiasing);
-       //painter.setBackground(Qt::black);
-       m_painter->setBrush(Qt::white);
-       m_painter->eraseRect( 0, 0, m_width, m_height );
+       delete [] bitmap;
 
-       //painter.drawText ( 0, 0, s );
-       m_painter->drawText ( 0, m_height, string );
-       m_painter->end();
+       m_displayList = glGenLists(1);
+       if( m_displayList == 0 ) return false;
 
-       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
-       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-       glwidget->bindTexture( *m_image );
-       glMatrixMode( GL_PROJECTION );
-       glPushMatrix();
-       glLoadIdentity();
-       glOrtho( 0, glwidget->width(), 0, glwidget->height(), -1, 1 );
-       glMatrixMode( GL_MODELVIEW );
-       glPushMatrix();
-       glLoadIdentity();
+       glNewList( m_displayList, GL_COMPILE );
+       glBindTexture( GL_TEXTURE_2D, m_texture );
        glBegin(GL_QUADS);
        glTexCoord2f( 0, 0);
-       glVertex2f( x , y );
+       glVertex2f( 0 , 0 );
        glTexCoord2f( 1, 0);
-       glVertex2f( x+m_width , y );
+       glVertex2f( m_width , 0 );
        glTexCoord2f( 1, 1);
-       glVertex2f( x+m_width , y+m_height );
+       glVertex2f( m_width, m_height );
        glTexCoord2f( 0, 1);
-       glVertex2f( x , y+m_height );
+       glVertex2f( 0 , m_height );
        glEnd();
-       glDisable( GL_TEXTURE_2D);
-       glDisable( GL_BLEND );
-       glPopMatrix();
+       glTranslatef( m_width, 0, 0 );
+       glEndList();
+
+       return true;
+}
+
+TextRenderer::TextRenderer()
+{
+       m_glwidget = 0;
+       m_isBetweenBeginAndEnd = false;
+}
+
+TextRenderer::~TextRenderer()
+{
+       QHash<QChar, CharRenderer *>::iterator i = m_charTable.begin();
+       while( i != m_charTable.end() )
+       {
+               delete i.value();
+               i = m_charTable.erase(i);
+        }
+}
+
+void TextRenderer::setup( const QGLWidget *glwidget, const QFont &font )
+{
+       if( m_glwidget ) return;
+       m_glwidget = glwidget;
+       m_font = font;
+}
+
+void TextRenderer::do_begin()
+{
+       m_wasEnabled_LIGHTING = glIsEnabled( GL_LIGHTING );
+       m_wasEnabled_FOG = glIsEnabled( GL_FOG );
+       m_wasEnabled_TEXTURE_2D = glIsEnabled( GL_TEXTURE_2D );
+       m_wasEnabled_BLEND = glIsEnabled( GL_BLEND );
+       m_wasEnabled_DEPTH_TEST = glIsEnabled( GL_DEPTH_TEST );
+       glDisable( GL_LIGHTING );
+       glDisable( GL_FOG );
+       glEnable( GL_TEXTURE_2D );
+       glEnable( GL_BLEND );
+       glDisable( GL_DEPTH_TEST );
+       glMatrixMode( GL_PROJECTION );
+       glPushMatrix();
+       glLoadIdentity();
+       glOrtho( 0, m_glwidget->width(), 0, m_glwidget->height(), -1, 1 );
+       glMatrixMode( GL_MODELVIEW );
+}
+
+void TextRenderer::begin()
+{
+       if( ! m_glwidget ) return;
+       if( m_isBetweenBeginAndEnd ) return;
+       m_isBetweenBeginAndEnd = true;
+       do_begin();
+}
+
+void TextRenderer::do_end()
+{
+       if( ! m_wasEnabled_TEXTURE_2D ) glDisable( GL_TEXTURE_2D);
+       if( ! m_wasEnabled_BLEND ) glDisable( GL_BLEND );
+       if( m_wasEnabled_DEPTH_TEST ) glEnable( GL_DEPTH_TEST );
+       if( m_wasEnabled_LIGHTING ) glEnable( GL_LIGHTING );
+       if( m_wasEnabled_FOG ) glEnable( GL_FOG );
        glMatrixMode( GL_PROJECTION );
        glPopMatrix();
        glMatrixMode( GL_MODELVIEW );
-       return true;
+}
+
+void TextRenderer::end()
+{
+       if( m_isBetweenBeginAndEnd ) do_end();
+       m_isBetweenBeginAndEnd = false;
+}
+
+void TextRenderer::print( int x, int y, const QString &string)
+{
+       if( ! m_glwidget ) return;
+       if( string.isEmpty() ) return;
+
+       if( ! m_isBetweenBeginAndEnd ) do_begin();
+       
+       glPushMatrix();
+       glLoadIdentity();
+       glTranslatef( x, y, 0 );
+       for(int i = 0; i < string.size(); i++)
+       {
+               if( m_charTable.contains( string[i] ) )
+                       m_charTable.value(string[i])->draw();
+               else
+               {
+                       CharRenderer *c = new CharRenderer;
+                       if( c->initialize( string[i], m_font ) )
+                       {
+                               m_charTable.insert( string[i], c);
+                               c->draw();
+                       }
+                       else delete c;
+               }
+       }
+       glPopMatrix();
+
+       if( ! m_isBetweenBeginAndEnd ) do_end();
 }
index 8d25721c856aaa9dc5c6ff3d13bc0d5d07c4f9c5..6279b5f308f5a98de680e9b28686200b08c3eba1 100644 (file)
@@ -16,6 +16,9 @@
 #include <QGLWidget>
 #include <QPainter>
 #include <QImage>
+#include <QChar>
+#include <QHash>
+#include <kdebug.h>
 
 /** USE_DOUBLE_PRECISION: if defined, use doubles instead of floats for
  * handling the model's geometric data. This does not seem to impact
@@ -286,18 +289,207 @@ class Cylinder : public VertexArray
                }
 };
 
-class TextPainter
+/** This is a helper class for TextRenderer, and should probably never be
+* used directly. See TextRenderer.
+*
+* The CharRenderer class represents a character stored as OpenGL rendering
+* data : a texture object and a display list mapping it on a quad and then
+* translating to the right of it.
+*
+* See the m_charTable member of TextRenderer for an example of use of
+* this class.
+*/
+class CharRenderer
 {
        protected:
+               /**
+                * The OpenGL texture object
+                */
+               GLuint m_texture;
+
+               /**
+                * The OpenGL display list
+                */
+               GLuint m_displayList;
+
+               /**
+                * Width and height in pixels of the rendered character
+                */
                int m_width, m_height;
-               QImage *m_image;
-               QPainter *m_painter;
-               QFontMetrics *m_fontMetrics;
 
        public:
-               TextPainter::TextPainter();
-               TextPainter::~TextPainter();
-               bool TextPainter::print( QGLWidget *glwidget, int x, int y, const QString &string);
+               CharRenderer();
+               ~CharRenderer();
+               bool initialize( QChar c, const QFont &font );
+               inline void draw()
+               {
+                       glCallList( m_displayList );
+               }
+};
+
+
+/** This class renders text inside a QGLWidget. It replaces the functionality
+* of QGLWidget::renderText(). The advantages over renderText() include:
+*  - supports any font, any character encoding supported by Qt
+*    (renderText is 8-bit-only and can only use "OpenGL-compatible" fonts)
+*  - does not use any library outside Qt (renderText uses FreeType on X11)
+*  - renders characters as textured quads instead of calling glDrawPixels,
+*    which does not make much of a difference on MesaGL, but can be a lot
+*    faster and safer with other (buggy) OpenGL implementations. It will also
+*    allow to add more graphical effects in the future, like rotation,
+*    if we ever need that.
+*  - the characters are stored as 8bpp Alpha, which takes 4 times less
+*    memory than the 32bpp RGBA used by renderText.
+*  - the characters are rendered on-the-fly on the first time they appear
+*    in a QString being printed. This is achieved using a QHash to test whether
+*    a character has already been rendered.
+*
+* Recommended usage:
+* The TextRender class is meant to be used from inside a child class of
+* QGLWidget, say MyGLWidget.
+*
+* In the declaration of MyGLWidget, please declare a TextRenderer member:
+
+class MyGLWidget : public QGLWidget
+{
+       ...
+       TextRenderer m_textRenderer;
+       ...
+};
+
+* Now, in the constructor of MyGLWidget, you please call setup()
+* along these lines:
+
+       QFont f;
+       f.setStyleHint( QFont::SansSerif, QFont::PreferAntialias );
+       m_textRenderer.setup( this, f );
+
+* The setup() method should be called only once, which means you have to choose
+* a font once and for all, in the lifetime of your TextRenderer. Any QFont can
+* be used, the above is just an example. Now, to actually render text, in
+* the MyGLWidget::paintGL() method, you can call
+
+       m_textRenderer.print( x, y, string );
+
+* where x,y are ints and string is any QString. If you want to choose a color,
+* please call glColor3f or glColor4f before calling print(). Of course you can
+* also call qglColor or Color::apply. You can achieve semitransparent text at
+* no additional cost by choosing a semitransparent color.
+*
+* If you wish to do several calls to print(), it will improve performance
+* to enclose them between a call to begin() and a call to end(), like that:
+
+       m_textRenderer.begin();
+       m_textRenderer.print( x1, y1, string1 );
+       m_textRenderer.print( x2, y2, string2 );
+       m_textRenderer.print( x3, y2, string3 );
+       m_textRenderer.end();
+
+* Please make sure, though, that no OpenGL state change occurs between begin()
+* and end(), except the state changes performed by the TextRenderer itself.
+* In other words, please avoid calling glSomething() between begin() and end(),
+* except if you are sure that this call won't perform a relevant state change.
+*
+* The print() method when called alone, or the begin()-print()-end() group,
+* do restore the OpenGL state as they found it, including the matrices.
+*
+* If you experience rendering problems, you can try the following:
+* - disable some OpenGL state bits. For instance, TextRenderer automatically
+*   disables fog and lighting during rendering, because it doesn't work
+*   correctly with them enabled. There probably are other OpenGL state bits
+*   that have to be disabled, so if your program enables some of them, you
+*   might have to disable them before rendering text.
+* - if you experience poor font quality, please consider using an antialiased
+*   font.
+*
+* @author Benoit Jacob
+*/
+
+class TextRenderer
+{
+       protected:
+               /**
+                * The font used for rendering the chars. This is set
+                * once and for all by setup(). Note that it is stored
+                * by value, so the caller doesn't have to keep it alive.
+                */
+               QFont m_font;
+               
+               /**
+                * This hash gives the correspondence table between QChars
+                * (the keys) and the corresponding CharRenderers (the values).
+                * Every time a QChar is being met, either it is found in this
+                * table, in which case it can be directly rendered, or it is not
+                * found, in which case a new CharRenderer is created for it,
+                * and added to this table.
+                */
+               QHash<QChar, CharRenderer*> m_charTable;
+
+               /**
+                * The QGLWidget in which to render. This is set
+                * once and for all by setup().
+                */
+               const QGLWidget *m_glwidget;
+
+               /**
+                * This equals true if begin() has been called, but end() hasn't
+                * since.
+                */
+               GLboolean m_isBetweenBeginAndEnd;
+
+               /**
+                * These members are used to remember the OpenGL state in order
+                * to be able to restore it after rendering. See do_end().
+                */
+               GLboolean m_wasEnabled_LIGHTING;
+               GLboolean m_wasEnabled_TEXTURE_2D;
+               GLboolean m_wasEnabled_FOG;
+               GLboolean m_wasEnabled_BLEND;
+               GLboolean m_wasEnabled_DEPTH_TEST;
+
+               /**
+                * Stores the relevant part of the OpenGL state, and prepares
+                * for rendering
+                */
+               void do_begin();
+
+               /**
+                * Restores the OpenGL state
+                */
+               void do_end();
+
+       public:
+               TextRenderer();
+               ~TextRenderer();
+               
+               /**
+                * This should be called only once, before any printing occurs.
+                * @param glwidget The QGLWidget in which to render.
+                * See m_glwidget member.
+                * @param font The QFont to use. See m_font member.
+                */
+               void setup( const QGLWidget *glwidget, const QFont &font );
+
+               /**
+                * Prints text at the position (x,y) in window coordinates
+                * (0,0) is the bottom left corner
+                * @param x the x-coordinate
+                * @param y the y-coordinate
+                * @param string the QString to print
+                */
+               void print( int x, int y, const QString &string);
+
+               /**
+                * Call this before doing multiple calls to print(). This is
+                * not necessary, but will improve performance. Don't forget,
+                * then, to call end() after.
+                */
+               void begin();
+
+               /**
+                * Call this after having called begin() and print().
+                */
+               void end();
 };
 
 } // namespace KalziumGL
index 3c3a4de87e6562454e428407b7eeaece6e3030c3..10cfb872c166b6c4a7199722827999286c0a84dd 100644 (file)
@@ -40,10 +40,14 @@ KalziumGLWidget::KalziumGLWidget( QWidget * parent )
        m_molecule = 0;
        m_detail = 0;
        m_useFog = false;
-       m_textPainter = 0;
        m_inZoom = false;
        m_inMeasure = false;
 
+       QFont f;
+       f.setStyleHint( QFont::SansSerif, QFont::PreferAntialias );
+       m_textRenderer.setup( this, f );
+
+
        ChooseStylePreset( PRESET_SPHERES_AND_BICOLOR_BONDS );
        
        setMinimumSize( 100,100 );
@@ -55,7 +59,6 @@ KalziumGLWidget::KalziumGLWidget( QWidget * parent )
 
 KalziumGLWidget::~KalziumGLWidget()
 {
-       if ( m_textPainter) delete m_textPainter;
 }
 
 void KalziumGLWidget::initializeGL()
@@ -65,8 +68,6 @@ void KalziumGLWidget::initializeGL()
        glEnable( GL_DEPTH_TEST );
        glDepthFunc( GL_LEQUAL );
        glEnable( GL_CULL_FACE );
-       glDisable( GL_BLEND );
-       //glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
 
        glMatrixMode( GL_MODELVIEW );
        glPushMatrix();
@@ -74,7 +75,8 @@ void KalziumGLWidget::initializeGL()
        glGetDoublev( GL_MODELVIEW_MATRIX, m_RotationMatrix );
        glPopMatrix();
 
-       glEnable(GL_LIGHTING);
+       glEnable( GL_RESCALE_NORMAL_EXT );
+
        glEnable(GL_LIGHT0);
 
        GLfloat ambientLight[] = { 0.4, 0.4, 0.4, 1.0 };
@@ -87,7 +89,6 @@ void KalziumGLWidget::initializeGL()
        glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight);
        glLightfv(GL_LIGHT0, GL_POSITION, position);
 
-       glEnable(GL_FOG);
        GLfloat fogColor[] = { 0.0, 0.0, 0.0, 1.0 };
        glFogfv( GL_FOG_COLOR, fogColor );
        glFogi( GL_FOG_MODE, GL_LINEAR );
@@ -105,14 +106,15 @@ void KalziumGLWidget::initializeGL()
        glEnableClientState( GL_NORMAL_ARRAY );
 
        setupObjects();
-
-       m_textPainter = new TextPainter;
 }
 
 void KalziumGLWidget::paintGL()
 {
        if( ! m_molecule )
        {
+               glColor3f( 0.0, 1.0, 0.6 );
+               glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
+               m_textRenderer.print( 20, height() - 40, i18n("Please load a molecule") );
                return;
        }
 
@@ -289,13 +291,13 @@ void KalziumGLWidget::paintGL()
                s = QString::number( 1000 * frames /
                        double( new_time - old_time ),
                        'f', 1 );
-               s += " frames per second" + QString( QChar( 962 ) );
+               s += " frames per second" ;
                frames = 0;
                old_time = new_time;
        }
 
        glColor3f( 1.0, 1.0, 0.0 );
-       m_textPainter->print( this, 20, 20, s );
+       m_textRenderer.print( 20, 20, s );
 
        update();
 #endif
@@ -526,11 +528,7 @@ void KalziumGLWidget::ChooseStylePreset( StylePreset stylePreset )
 void KalziumGLWidget::prepareMoleculeData()
 {
        // translate the molecule so that center has coords 0,0,0
-       //m_molecule->Center();
-       std::map<std::string, std::string> m;
-       m["c"] = "c";
-       m_molecule->DoTransformations(&m);
-
+       m_molecule->Center();
 
        // calculate the radius of the molecule
        // that is, the maximal distance between an atom of the molecule
index 29e58c9ada75ccc768b5fe85502be7c64f786b7d..ec135ee811778f41013f8bbfc823778b7c5844e0 100644 (file)
@@ -32,7 +32,7 @@ class KalziumGLWidget : public QGLWidget
        Q_OBJECT
 
        protected:
-               TextPainter *m_textPainter;
+               TextRenderer m_textRenderer;
 
                /**
                 * The geometric model of the sphere (used for atoms).