PDA

View Full Version : How to draw text to a QPixmap and then draw the pixmap to a QGLWidget? (PyQt4)



MasterMuddler
10th February 2011, 19:08
Hi guys,

I would like to render text to a QPixmap and then draw the QPixmap to a QGLWidget that will be presented on screen. The reason I want to render text to the QPixmap is so that I can set the background color of the box bounding the text to be something different than the background already on the QGLWidget. Here is the code I have so far, with client being the QGLWidget and self._image being the QPixmap.

def show(self, client, x_pos, y_pos, width, height):
glEnable(GL_TEXTURE_2D)
tex_id = client.bindTexture(self._image)
self._painter.begin(self._image)
self._painter.fillRect(self._image.rect(), self._background_color)
self._painter.setPen(self._text_color)
self._painter.setFont(self._font)
self._painter.drawText(self._rect, QtCore.Qt.TextExpandTabs | QtCore.Qt.TextSingleLine | \
QtCore.Qt.AlignLeft, self._text)
self._painter.end()
glTranslate(x_pos/width, y_pos/height, 0)
width_norm = self.width()/width
height_norm = self.height()/height
glBegin(GL_QUADS)
glTexCoord(0.0, 0.0); glVertex(-width_norm, height_norm, 0.0)
glTexCoord(1.0, 0.0); glVertex(width_norm, height_norm, 0.0)
glTexCoord(1.0, 1.0); glVertex(width_norm, -height_norm, 0.0)
glTexCoord(0.0, 1.0); glVertex(-width_norm, -height_norm, 0.0)
glEnd()
glTranslate(-x_pos/width, -y_pos/height, 0)
client.deleteTexture(tex_id)
glDisable(GL_TEXTURE_2D)
show is called as part of QGLWidget paintEvent.

All I get with this is painting unallocated memory within the extent of what should be the text, using the current OpenGL drawing color.

Any suggestions?

MasterMuddler
10th February 2011, 21:33
I figured out to abandon the intervening QPixmap and use QPainter::fillRect before drawing the text. I've also got text working with the following code. Note that the class is now descended from QPainter.


def draw(self, client, x_pos, y_pos):
self.begin(client)
area = client.rect()
self.setFont(self._font)
self.setPen(self._text_color)
self.setBrush(QtCore.Qt.NoBrush)
self._rect.moveCenter(QtCore.QPoint(x_pos+area.wid th()/2, y_pos+area.height()/2))
self.fillRect(self._rect, self._background_color)
self.drawText(self._rect, QtCore.Qt.TextExpandTabs, self._text)
self.end()

Now however whenever I draw to the client, the painter object redraws the entire client space with a white background. How do I only draw that portion of the client that is indicated by the self._rect object?

wysota
10th February 2011, 21:53
QPainter::rect() returns the entire widget rectangle. Besides, for QGLWidget always the whole widget is redrawn.

MasterMuddler
10th February 2011, 22:27
client is a QGLWidget, not a QPainter.

Are you saying it is impossible to perform several such operations consecutively on a QGLWidget without the QGLWidget being entirely redrawn? For example, if i wish to call draw() two times between calls to paintGL so that there are two text objects on the QGLWidget object, then the second text object will overwrite the first?

wysota
10th February 2011, 22:34
client is a QGLWidget, not a QPainter.
Yet you are calling QPainter's rect:

self.fillRect(self._rect, self._background_color)


Are you saying it is impossible to perform several such operations consecutively on a QGLWidget without the QGLWidget being entirely redrawn? For example, if i wish to call draw() two times between calls to paintGL so that there are two text objects on the QGLWidget object, then the second text object will overwrite the first?
First of all you can't draw on the widget when the GL context is not active so unless you make it active yourself, you can only draw during paintGL(). Second of all each paintGL() causes the whole QGLWidget to be rerendered. That's all I'm saying, I don't know if it clears your doubts or not.

MasterMuddler
10th February 2011, 22:40
First off thanks for your help.

The object that draw() is a member of is stored in a list that is iterated through whenever paintGL is called. So if there are two objects derived from QPainter in the list, then draw() will be called on each of them in paintGL, and after makeCurrent() has been called on the QGLWidget being drawn to. The problem that occurs is that first the black background of the QGLWidget is replaced with a white background after calling fillRect and drawText the first time, and the text generated from this first call to draw() is replaced when draw() is called again.

Is there any way to prevent all of the area of QGLWidget outside the area drawn to by fillRect and drawText to not be touched, or to be drawn transparently? I don't want the background to be drawn white at any time. I just want the text objects to be able to accumulate on the black background.

wysota
10th February 2011, 22:49
So if there are two objects derived from QPainter in the list,
I have to admit the concept of deriving something from QPainter seems very odd to me. What is the point of doing that?


Is there any way to prevent all of the area of QGLWidget outside the area drawn to by fillRect and drawText to not be touched, or to be drawn transparently?
But you are drawing on the whole area of the widget! You are calling fillRect() on the painter's rect which holds the widget's rect. There is no "outside" here.


I don't want the background to be drawn white at any time. I just want the text objects to be able to accumulate on the black background.
If you don't want something to be drawn then don't draw it. I don't understand what your problem is. I think the real problem is that you incorrectly assume what QPainter is if you derive your own classes from it and this causes an effect different from what you expect.

MasterMuddler
10th February 2011, 22:53
You are calling fillRect() on the painter's rect which holds the widget's rect. There is no "outside" here.

self._rect is not the entire area of the QPainter. It's just a subsection and this works corrctly. The problem is that everything else was drawn white.

However I did not realize that calling QPainter::begin made the entire client area subject to the painter. Is there a method of QGLWidget to return a subsection of its client area as something that can be passed to QPainter?

wysota
10th February 2011, 23:04
However I did not realize that calling QPainter::begin made the entire client area subject to the painter. Is there a method of QGLWidget to return a subsection of its client area as something that can be passed to QPainter?
QPainter works on a QPaintDevice. QGLWidget is a QPaintDevice. "Subsection of QGLWidget" is not a QPaintDevice.

How does your complete paintGL() implementation look like and why are you subclassing QPainter? Doesn't the white colour come from setting the OpenGL fill colour to white?

MasterMuddler
10th February 2011, 23:21
I'm subclassing QPainter because the object that draw is a part of is an object that paints, for example, only text.

Here is what the widget looks like after the first call to draw():
5916
Here is what the widget looks like after the second call to draw():
5917
The second image should contain both text objects.

If I set glColor to be black before drawing, I still get the white background. glClearColor is set to black as well.

wysota
10th February 2011, 23:31
I'm subclassing QPainter because the object that draw is a part of is an object that paints, for example, only text.
This doesn't justify inheriting QPainter. Imagine QPainter is a tool box with different tools inside like hammers, screwdrivers and such. If you want to build a fence, you don't say that your fence is a special kind of tool box but rather you use tools available in the tool box to create the fence. You can also combine tools you have to do work of other (custom) tools. The tool box itself is not modified in any way here.