From: BenoƮt Jacob Date: Sun, 2 Jul 2006 11:35:46 +0000 (+0000) Subject: main change: added TextRenderer class (and CharRenderer helper class) X-Git-Tag: v3.80.3~103^2~32 X-Git-Url: https://git.rmz.fi/?a=commitdiff_plain;h=d8d1d89842a4ca7fafa23e97e3ca1b6fe0ff0fd9;p=libqmvoc.git main change: added TextRenderer class (and CharRenderer helper class) 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 --- diff --git a/kalzium/kalziumglhelperclasses.cpp b/kalzium/kalziumglhelperclasses.cpp index 5756a70..ba7a135 100644 --- a/kalzium/kalziumglhelperclasses.cpp +++ b/kalzium/kalziumglhelperclasses.cpp @@ -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::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(); } diff --git a/kalzium/kalziumglhelperclasses.h b/kalzium/kalziumglhelperclasses.h index 8d25721..6279b5f 100644 --- a/kalzium/kalziumglhelperclasses.h +++ b/kalzium/kalziumglhelperclasses.h @@ -16,6 +16,9 @@ #include #include #include +#include +#include +#include /** 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 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 diff --git a/kalzium/kalziumglwidget.cpp b/kalzium/kalziumglwidget.cpp index 3c3a4de..10cfb87 100644 --- a/kalzium/kalziumglwidget.cpp +++ b/kalzium/kalziumglwidget.cpp @@ -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 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 diff --git a/kalzium/kalziumglwidget.h b/kalzium/kalziumglwidget.h index 29e58c9..ec135ee 100644 --- a/kalzium/kalziumglwidget.h +++ b/kalzium/kalziumglwidget.h @@ -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).