• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

KGLLib

textrenderer.cpp

Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2007-2008 Benoit Jacob
00003  * Copyright (C) 2008 Rivo Laks <rivolaks@hot.ee>
00004  *
00005  * This library is free software; you can redistribute it and/or
00006  * modify it under the terms of the GNU Lesser General Public
00007  * License as published by the Free Software Foundation; either 
00008  * version 2.1 of the License, or (at your option) any later version.
00009  *
00010  * This library is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013  * Library General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU Lesser General Public 
00016  * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
00017  */
00018 
00019 /*
00020  This file has been taken from Avogadro project - http://avogadro.sourceforge.net/
00021  As such, it uses different coding style than the rest of the KGLLib. This is
00022  meant to minimize differences between this version and the one used by
00023  Avogadro, so that bugfixes, etc made to one version could easily be merged
00024  into the other one.
00025  */
00026 
00027 #include "textrenderer.h"
00028 
00029 #include "glwidget.h"
00030 
00031 #include <QPainter>
00032 #include <QHash>
00033 #include <QMap>
00034 #include <QDebug>
00035 
00036 namespace {
00037 
00038 #define OUTLINE_WIDTH     3
00039 const int OUTLINE_BRUSH[2*OUTLINE_WIDTH+1][2*OUTLINE_WIDTH+1]
00040 = { { 10, 30,  45,  50,  45,  30,  10 },
00041   { 30, 65,  85,  100,  85, 65,  30 },
00042   { 45, 85,  200, 256, 200, 85,  45 },
00043   { 50, 100, 256, 256, 256, 100, 50},
00044   { 45, 85,  200, 256, 200, 85,  45 },
00045   { 30, 65,  85,  100,  85, 65,  30 },
00046   { 10, 30,  45,  50,  45,  30,  10 } };
00047 
00048 /*
00049    = { { 40,  75,  100,  75, 40 },
00050    { 75,  200, 256, 200, 75 },
00051    { 100, 256, 256, 256, 100 },
00052    { 75,  200, 256, 200, 75 },
00053    { 40,  75,  100,  75, 40 } };
00054    */
00055 /*  = { { 200, 256, 200 },
00056     { 256, 256, 256 },
00057     { 200, 256, 200 } }; */
00058 
00069   class CharRenderer
00070   {
00071     protected:
00072       GLuint m_glyphTexture;
00073       GLuint m_outlineTexture;
00074 
00075       GLuint m_quadDisplayList;
00076 
00077       GLenum m_textureTarget;
00078 
00082       int m_realwidth, m_realheight;
00083 
00084     public:
00085       CharRenderer();
00086       ~CharRenderer();
00087 
00089       bool initialize( QChar c, const QFont &font, GLenum textureTarget );
00090 
00092       void draw(const float *color) const;
00093 
00095       inline int height() const { return m_realheight; }
00096 
00098       inline int width() const { return m_realwidth; }
00099 
00100       inline void drawOutline() const
00101       {
00102         glBindTexture(m_textureTarget, m_outlineTexture);
00103         glCallList( m_quadDisplayList );
00104       }
00105 
00106       inline void drawGlyph() const
00107       {
00108         glBindTexture(m_textureTarget, m_glyphTexture);
00109         glCallList( m_quadDisplayList );
00110       }
00111   };
00112 
00113   CharRenderer::CharRenderer()
00114   {
00115     m_glyphTexture = 0;
00116     m_outlineTexture = 0;
00117     m_quadDisplayList = 0;
00118   }
00119 
00120   CharRenderer::~CharRenderer()
00121   {
00122     if( m_glyphTexture ) glDeleteTextures( 1, &m_glyphTexture );
00123     if( m_outlineTexture ) glDeleteTextures( 1, &m_outlineTexture );
00124     if( m_quadDisplayList ) glDeleteLists( m_quadDisplayList, 1 );
00125   }
00126 
00127   static void normalizeTexSize( GLenum textureTarget,
00128       int& texwidth, int& texheight )
00129   {
00130     // if the texture target is GL_TEXTURE_2D, that means that
00131     // the texture_rectangle OpenGL extension is unsupported and we must
00132     // use only square, power-of-two textures.
00133     if( textureTarget == GL_TEXTURE_2D )
00134     {
00135       int x = qMax( texwidth, texheight );
00136       // find next power of two
00137       int n;
00138       for(n = 1; n < x; n = n << 1) {}
00139       // the texture must be square, and its size must be a power of two.
00140       texwidth = texheight = n;
00141     }
00142   }
00143 
00144   bool CharRenderer::initialize( QChar c, const QFont &font, GLenum textureTarget )
00145   {
00146     if( m_quadDisplayList ) return true;
00147     m_textureTarget = textureTarget;
00148     // *** STEP 1 : render the character to a QImage ***
00149 
00150     // compute the size of the image to create
00151     const QFontMetrics fontMetrics ( font );
00152     m_realwidth = fontMetrics.width(c);
00153     m_realheight = fontMetrics.height();
00154     if(m_realwidth == 0 || m_realheight == 0) return false;
00155     int texwidth  =  m_realwidth + 2 * OUTLINE_WIDTH;
00156     int texheight = m_realheight + 2 * OUTLINE_WIDTH;
00157     normalizeTexSize(textureTarget, texwidth, texheight);
00158 
00159     // create a new image
00160     QImage image( texwidth, texheight, QImage::Format_RGB32 );
00161     QPainter painter;
00162     // start painting the image
00163     painter.begin( &image );
00164     painter.setFont( font );
00165     painter.setRenderHint( QPainter::TextAntialiasing );
00166     painter.setBackground( Qt::black );
00167     painter.eraseRect( image.rect() );
00168     // use an artificial blue color. This image is only used internally anyway.
00169     painter.setPen( Qt::blue );
00170     // actually paint the character. The position seems right at least with Helvetica
00171     // at various sizes, I didn't try other fonts. If in the future a user complains about
00172     // the text being clamped to the top/bottom, change this line.
00173     painter.drawText ( 1, m_realheight
00174         + 2 * OUTLINE_WIDTH
00175         - painter.fontMetrics().descent(),
00176         c );
00177     // end painting the image
00178     painter.end();
00179 
00180     // *** STEP 2 : extract the raw bitmap from the image ***
00181 
00182     // --> explanation: the image we just rendered is RGB, but actually all the
00183     //     data is in the B channel because we painted in blue. Now we extract
00184     //     this blue channel into a separate bitmap that'll be faster to manipulate
00185     //     in what follows.
00186 
00187     int *rawbitmap = new int[ texwidth * texheight ];
00188     if( ! rawbitmap ) return false;
00189     int n = 0;
00190     // loop over the pixels of the image, in reverse y direction
00191     for( int j = texheight - 1; j >= 0; j-- )
00192       for( int i = 0; i < texwidth; i++, n++ )
00193       {
00194         double x = qBlue( image.pixel( i, j ) ) / 255.0;
00195         double y = pow(x, 0.75); /* this applies a gamma correction with gamma factor 0.5.
00196                   the effect of this is to concentrate the intensities in
00197                   the large values. This results in a slightly bolder-looking
00198                   font, which is more suitable for outlining. More importantly,
00199                   this makes more intense the otherwise dim shades. As the
00200                   intensity of the outline is proportional to the intensity of
00201                   the rawbitmap, dim shades give a dim outline which is
00202                   bad for readability, so we don't want a whole part of a glyph
00203                   to use a dim shade. Now the problem is that on my setup,
00204                   e.g. the small "n" had one of its legs spread across two columns
00205                   of pixels, each with a dim shade.*/
00206         rawbitmap[n] = static_cast<int>(255.0 * y);
00207       }
00208 
00209     // *** STEP 3 : compute the neighborhood map from the raw bitmap ***
00210 
00211     // --> explanation: we apply a convolution filter to the raw bitmap
00212     //     to produce a new map each pixel is associated a float telling how
00213     //     much it is surrounded by other pixels.
00214 
00215     int *neighborhood = new int[ texwidth * texheight ];
00216     if( ! neighborhood ) return false;
00217     for( int i = 0; i < texheight * texwidth; i++)
00218       neighborhood[i] = 0;
00219 
00220     for( int i = 0; i < texheight; i++ ) {
00221       for( int j = 0; j < texwidth; j++ ) {
00222         n = j + i * texwidth;
00223         for( int di = -OUTLINE_WIDTH; di <= OUTLINE_WIDTH; di++ ) {
00224           for( int dj = -OUTLINE_WIDTH; dj <= OUTLINE_WIDTH; dj++ ) {
00225             int fi = i + di;
00226             int fj = j + dj;
00227             if( fi >= 0 && fi < texheight && fj >= 0 && fj < texwidth ) {
00228               int fn = fj + fi * texwidth;
00229               neighborhood[fn]
00230                 = qMax(
00231                     neighborhood[fn],
00232                     rawbitmap[n]
00233                     * OUTLINE_BRUSH[OUTLINE_WIDTH + di]
00234                     [OUTLINE_WIDTH + dj]);
00235             }
00236           }
00237         }
00238       }
00239     }
00240 
00241     // *** STEP 4 : compute the final bitmaps  ***
00242     // --> explanation: we build the bitmaps that will be passed to OpenGL for texturing.
00243 
00244     GLubyte *glyphbitmap = new GLubyte[ texwidth * texheight ];
00245     if( ! glyphbitmap ) return false;
00246     GLubyte *outlinebitmap = new GLubyte[ texwidth * texheight ];
00247     if( ! outlinebitmap ) return false;
00248 
00249     for( int n = 0; n < texwidth * texheight; n++ )
00250     {
00251       glyphbitmap[n] = static_cast<GLubyte>(rawbitmap[n]);
00252       int alpha = (neighborhood[n] >> 8) + rawbitmap[n];
00253       if( alpha > 255 ) {
00254         alpha = 255;
00255       }
00256       outlinebitmap[n] = static_cast<GLubyte>(alpha);
00257     }
00258 
00259     delete [] rawbitmap;
00260     delete [] neighborhood;
00261 
00262     // *** STEP 5 : construct OpenGL textures from the final bitmaps ***
00263 
00264     glGenTextures( 1, &m_glyphTexture );
00265     if( ! m_glyphTexture ) return false;
00266     glGenTextures( 1, &m_outlineTexture );
00267     if( ! m_outlineTexture ) return false;
00268 
00269     glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
00270 
00271     glBindTexture( textureTarget, m_glyphTexture );
00272     glTexImage2D(
00273         textureTarget,
00274         0,
00275         GL_ALPHA,
00276         texwidth,
00277         texheight,
00278         0,
00279         GL_ALPHA,
00280         GL_UNSIGNED_BYTE,
00281         glyphbitmap );
00282 
00283     glTexParameteri( textureTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
00284     glTexParameteri( textureTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
00285 
00286     glBindTexture( textureTarget, m_outlineTexture );
00287     glTexImage2D(
00288         textureTarget,
00289         0,
00290         GL_ALPHA,
00291         texwidth,
00292         texheight,
00293         0,
00294         GL_ALPHA,
00295         GL_UNSIGNED_BYTE,
00296         outlinebitmap );
00297 
00298     glTexParameteri( textureTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
00299     glTexParameteri( textureTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
00300 
00301     // the texture data is now kept alive by OpenGL. It's time to free the bitmaps.
00302     delete [] glyphbitmap;
00303     delete [] outlinebitmap;
00304 
00305     // *** STEP 6 : compile the display list ***
00306 
00307     m_quadDisplayList = glGenLists(1);
00308     if( ! m_quadDisplayList ) return false;
00309 
00310     int texcoord_width = (textureTarget == GL_TEXTURE_2D) ? 1 : texwidth;
00311     int texcoord_height = (textureTarget == GL_TEXTURE_2D) ? 1 : texheight;
00312 
00313     glNewList( m_quadDisplayList, GL_COMPILE );
00314     glBegin( GL_QUADS );
00315     glTexCoord2i( 0, 0);
00316     glVertex2f( 0 , -texheight );
00317     glTexCoord2i( texcoord_width, 0);
00318     glVertex2f( texwidth , -texheight );
00319     glTexCoord2i( texcoord_width, texcoord_height);
00320     glVertex2f( texwidth, 0 );
00321     glTexCoord2i( 0, texcoord_height);
00322     glVertex2f( 0 , 0 );
00323     glEnd();
00324     glEndList();
00325 
00326     return true;
00327   }
00328 
00329 } // anonymous namespace
00330 
00331 namespace KGLLib {
00332 
00333   class TextRenderer::Private
00334   {
00335     public:
00336 
00337       Private() : initialized(false) {}
00338       ~Private() {}
00339 
00348       QMap<QFont, QHash<QChar, CharRenderer*> > charTable;
00349 
00354       GLWidget *glwidget;
00355 
00356       GLboolean textmode;
00357 
00358       bool initialized;
00359 
00360       GLenum textureTarget;
00361 
00362       void do_draw(const QString &string, const QFont& font);
00363   };
00364 
00365   void TextRenderer::Private::do_draw( const QString &string, const QFont& font )
00366   {
00367     int i;
00368     GLfloat color[4];
00369     glGetFloatv(GL_CURRENT_COLOR, color);
00370 
00371     QHash<QChar, CharRenderer*>& chars = charTable[font];
00372     const QFontMetrics fontMetrics ( font );
00373 
00374     // Pass 1: render and cache the glyphs that are not yet cached.
00375     for( i = 0; i < string.size(); i++ )
00376     {
00377       if( ! chars.contains( string[i]) )
00378       {
00379         CharRenderer *c = new CharRenderer;
00380         if(!c->initialize( string[i], font, textureTarget ) )
00381         {
00382           delete c;
00383           c = new CharRenderer;
00384           qDebug() << "Character " << string[i]
00385             << "(unicode" << string[i].unicode()
00386             << ") failed to render using the following font:";
00387           qDebug() << font.toString();
00388           if(!c->initialize( '*', font, textureTarget ))
00389           {
00390             qDebug() << "Can't render even a simple character (*).";
00391             qDebug() << "Are you using a bad font, or what?";
00392             qDebug() << "The font being used is:";
00393             qDebug() << font.toString();
00394             assert(false);
00395           }
00396         }
00397         chars.insert( string[i], c);
00398       }
00399     }
00400 
00401     // Pass 2: render the outline
00402     glColor4f(0,0,0,color[3]);
00403     glPushMatrix();
00404     for( i = 0; i < string.size(); i++ )
00405     {
00406       chars.value( string[i] )->drawOutline();
00407       glTranslatef(fontMetrics.charWidth(string, i), 0, 0);
00408     }
00409     glPopMatrix();
00410 
00411     // Pass 3: render the glyphs themselves
00412     glPushMatrix();
00413     glColor4fv(color);
00414     for( i = 0; i < string.size(); i++ )
00415     {
00416       chars.value( string[i] )->drawGlyph();
00417       glTranslatef(fontMetrics.charWidth(string, i), 0, 0);
00418     }
00419     glPopMatrix();
00420   }
00421 
00422 
00423   TextRenderer::TextRenderer() : d(new Private)
00424   {
00425     d->glwidget = 0;
00426     d->textmode = false;
00427   }
00428 
00429   TextRenderer::~TextRenderer()
00430   {
00431     QMap<QFont, QHash<QChar, CharRenderer*> >::iterator j = d->charTable.begin();
00432     for(; j != d->charTable.end(); j++ )
00433     {
00434       QHash<QChar, CharRenderer *>::iterator i = j.value().begin();
00435       for(; i != j.value().end(); i++ )
00436       {
00437         delete i.value();
00438       }
00439     }
00440     delete d;
00441   }
00442 
00443   // void TextRenderer::setGLWidget( GLWidget *glwidget )
00444   // {
00445   //   d->glwidget = glwidget;
00446   //   d->font = d->glwidget->font();
00447   // }
00448 
00449 
00450   void TextRenderer::begin(GLWidget *widget)
00451   {
00452     if(!d->initialized) {
00453       if(GLEW_ARB_texture_rectangle) {
00454         d->textureTarget = GL_TEXTURE_RECTANGLE_ARB;
00455         qDebug() << "OpenGL extension GL_ARB_texture_rectangle is present.";
00456       } else {
00457         d->textureTarget = GL_TEXTURE_2D;
00458         qDebug() << "OpenGL extension GL_ARB_texture_rectangle is absent.";
00459       }
00460       d->initialized = true;
00461     }
00462 
00463     // already called begin
00464     if(d->glwidget == widget)
00465     {
00466       return;
00467     }
00468 
00469 
00470     // make sure we called ::end
00471     assert(!d->glwidget);
00472 
00473     d->glwidget = widget;
00474     d->textmode = true;
00475     //   glPushAttrib(GL_ENABLE_BIT | GL_DEPTH_BUFFER_BIT);
00476     glPushAttrib(GL_ALL_ATTRIB_BITS);
00477     glDisable(GL_LIGHTING);
00478     glDisable(GL_FOG);
00479     glDisable(GL_CULL_FACE);
00480     glEnable(d->textureTarget);
00481     glEnable(GL_BLEND);
00482     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
00483     glDepthMask(GL_FALSE);
00484     glMatrixMode(GL_PROJECTION);
00485     glPushMatrix();
00486     glLoadIdentity();
00487     glOrtho( 0, d->glwidget->width(), 0, d->glwidget->height(), 0, 1 );
00488     glMatrixMode( GL_MODELVIEW );
00489   }
00490 
00491   void TextRenderer::end()
00492   {
00493     if(d->glwidget) {
00494       assert(d->textmode);
00495       glMatrixMode( GL_PROJECTION );
00496       glPopMatrix();
00497       glMatrixMode( GL_MODELVIEW );
00498       glPopAttrib();
00499       d->textmode = false;
00500       d->glwidget = 0;
00501     }
00502   }
00503 
00504   int TextRenderer::draw( int x, int y, const QString &string, const QFont& font )
00505   {
00506     assert(d->textmode);
00507     if( string.isEmpty() ) return 0;
00508     glPushMatrix();
00509     glLoadIdentity();
00510     const QFontMetrics fontMetrics ( font );
00511     glTranslatef( x, d->glwidget->height() - y, 0 );
00512 
00513     // Split the string into lines
00514     QStringList lines = string.split('\n');
00515     foreach (QString line, lines) {
00516         d->do_draw(line, font);
00517         // Move one line down
00518         glTranslatef(0, -fontMetrics.lineSpacing(), 0);
00519     }
00520 
00521     glPopMatrix();
00522     return lines.count() * fontMetrics.lineSpacing();
00523   }
00524 
00525   int TextRenderer::draw( const QRect& rect, const QString &string, int flags, const QFont& font)
00526   {
00527       glDisable(GL_DEPTH_TEST);
00528       const QFontMetrics fontMetrics(font);
00529       // First break the string into lines
00530       QStringList lines;
00531       if (flags & Qt::TextSingleLine) {
00532           // Entire string is a single line
00533           lines.append(string);
00534       } else {
00535           // Break at newlines
00536           lines = string.split('\n');
00537       }
00538       if (flags & Qt::TextWordWrap) {
00539           // Wrap the lines if necessary
00540           QStringList newlines;
00541           foreach (QString line, lines) {
00542               QString newline;
00543               int newlinew = 0;
00544               QStringList words = line.simplified().split(' ');
00545               foreach (QString word, words) {
00546                   int wordw = fontMetrics.width(" " + word);
00547                   if (newlinew + wordw > rect.width()) {
00548                       // Break the line here
00549                       newlines.append(newline);
00550                       newline = QString();
00551                       newlinew = 0;
00552                   }
00553                   if (!newline.isEmpty()) {
00554                       newline += ' ';
00555                   }
00556                   newline += word;
00557                   newlinew += wordw;
00558               }
00559               if (!newline.isEmpty()) {
00560                   newlines.append(newline);
00561               }
00562           }
00563           lines = newlines;
00564       }
00565 
00566       // Calculate text height in pixels
00567       int texth = lines.count() * fontMetrics.lineSpacing();
00568 
00569       // Calculate vertical position of the first line
00570       int y = rect.top();
00571       if (flags & Qt::AlignBottom) {
00572           y = rect.bottom() - texth;
00573       } else if (flags & Qt::AlignVCenter) {
00574           y = rect.center().y() - texth/2;
00575       }
00576       // Draw each line
00577       for (int i = 0; i < lines.count(); i++) {
00578           const QString& line = lines[i];
00579           // Calculate horizontal position
00580           int x = rect.left();
00581           if (flags & Qt::AlignRight) {
00582               x = rect.right() - fontMetrics.width(line);
00583           } else if (flags & Qt::AlignHCenter) {
00584               x = rect.center().x() - fontMetrics.width(line)/2;
00585           }
00586           // Draw
00587           y += draw(x, y, line, font);
00588       }
00589       return texth;
00590   }
00591 
00592   int TextRenderer::draw( const Eigen::Vector3d &pos, const QString &string, const QFont& font )
00593   {
00594     assert(d->textmode);
00595     if( string.isEmpty() ) return 0;
00596 
00597     const QFontMetrics fontMetrics ( font );
00598     int w = fontMetrics.width(string);
00599     int h = fontMetrics.height();
00600 
00601     // TODO: uncomment once Camera has project() method
00602     Eigen::Vector3d wincoords;// = d->glwidget->camera()->project(pos);
00603 
00604     // project is in QT window coordinates
00605     wincoords.y() = d->glwidget->height() - wincoords.y();
00606 
00607     wincoords.x() -= w/2;
00608     wincoords.y() += h/2;
00609 
00610     glPushMatrix();
00611     glLoadIdentity();
00612     glTranslatef( static_cast<int>(wincoords.x()),
00613         static_cast<int>(wincoords.y()),
00614         -wincoords.z() );
00615     d->do_draw(string, font);
00616     glPopMatrix();
00617     return fontMetrics.lineSpacing();
00618   }
00619 
00620   bool TextRenderer::isActive()
00621   {
00622     return d->glwidget;
00623   }
00624 
00625 } // namespace KGLLib

KGLLib

Skip menu "KGLLib"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

API Reference

Skip menu "API Reference"
  • KGLLib
Generated for API Reference by doxygen 1.5.4
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal