PDA

View Full Version : QGLWidget: problem with very large images



gitarooLegend
30th August 2011, 20:59
Hi,

I'm using a QGLWidget to display images in a media player. Everything has been working fine until recently I came across a corner case: I was asked to support panoramic images that are about 256x10496. Once I tried loading an image up, the whole screen flashes once, the QGLWidget displays all black, and message comes up at the bottom of my screen saying:

"The color scheme has been changed to Windows 7 Basic. A running program isn't compatible with certain visual elements of Windows."

The openGL code i'm using takes the image (which is formatted as an array of unsigned shorts(16 bits per pixel)) and converts it to a 2D texture, using glTexImage2D(), and renders it to the screen. A previous version of my software used QPainter on a QLabel (we switched for performance reasons and so we could take advantage of OpenGL's support of 16 bit per pixel image formats instead of needing to crush it down to 8bpp), and for some reason this handles the large image formats no problem. At first it seemed to me that the OpenGL code was causing the problem by overwhelming the max viewport dimension size of the video card (which was 8192), and that QPainter was getting around this by only attempting to draw what was in the QScrollArea's viewport. However, when I tried using a QPainter in the case of large images instead of the OpenGL code (all still within the QGLWidget class), the same thing kept happening. Why does the QPainter on a QLabel accept very large images with no problem while QPainter on a QGLWidget does not?

Example:


MyClass::paintGL()
{
glClear( GL_COLOR_BUFFER_BIT );

glDisable(GL_DEPTH_TEST);

unsigned int testWidth = 256;
unsigned int testHeight = 10496;
unsigned int testBpp = 16;
unsigned int colorIncr = (65536/testWidth);

unsigned short* testPattern = new unsigned short[testWidth*testHeight];
unsigned int color = 0;
for(unsigned int i = 0; i < testWidth*testHeight; ++i){
testPattern[i] = color;
color += colorIncr;
if(color >= 65536)
color = 0;
}

if(useOpenGL){
//OpenGL method: doesn't work
GLvoid* pixels = (GLushort*)testPattern;
glEnable(GL_TEXTURE_2D);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);


GLuint myFrameTexture;
glGenTextures(1, &myFrameTexture);
glBindTexture(GL_TEXTURE_2D, (myFrameTexture));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE,
testWidth, testHeight,
0, GL_LUMINANCE, GL_UNSIGNED_SHORT, pixels);
float startX = 0.0f;
float startY = 0.0f;
float endX = testWidth;
float endY = testheight;

glBegin(GL_QUADS);
glTexCoord2f(0.0,1.0);
glVertex2f(startX, startY);
glTexCoord2f(0.0,0.0);
glVertex2f(startX, endY);
glTexCoord2f(1.0,0.0);
glVertex2f(endX, endY);
glTexCoord2f(1.0,1.0);
glVertex2f(endX, startY);
glEnd();

glDisable(GL_TEXTURE_2D);
glDeleteTextures(1, &myFrameTexture);
}else{
//QPainter method, also doesn't work.
QPainter p(this);
p.scale(testWidth, testHeight);
QImage* image = new QImage(testWidth, testHeight,
QImage::Format_Indexed8);
image->setNumColors(256);
for(int i = 0; i < 256; i++)
image->setColor(i, QRGB(i,i,i));
//get 8bpp version of test pattern
unsigned char* test8bpp = new unsigned char[testHeight*testWidth];
for(int i = 0; i < (testHeight*testWidth); i++){
test8bpp[i] = testPattern[i] >> 8;
}
for(int i = 0; i < testHeight; i++){
unsigned char* row = image->scanLine(i);
memcpy(row, test8bpp+(i*testWidth), testWidth);
}
p.drawImage(0,0,*image);
delete [] test8bpp;
}
delete [] testPattern;
}


Sorry for the long winded code example. One thing to note here is that the QPainter code called from within paintGL does work with large image formats when it is placed inside of a widget which subclasses QLabel instead of QGLWidget and inside the "paintEvent()" function. I have also tested the QPainter code with normal sized images (less than 8192 in both dimensions) and it properly displays those images, but when I give either the OpenGL or QPainter code inside of a QGLWidget an image with dimensions exceeding 8192, it makes an all white or all black box of that size, and windows switches to basic color mode.

I realize this is a pretty ridiculous corner case at the moment, but nonetheless I still need to handle it. Any input/suggestions would be appreciated.

Thanks.

wysota
30th August 2011, 21:13
The problem probably is caused by your OpenGL implementation (or rather hardware) not able to handle such large textures. Check that using appropriate GL calls and cut the image to several pieces to compensate.

gitarooLegend
30th August 2011, 21:28
Thanks for the reply. I had previously tried cutting it down into several pieces when I thought the limitation was imposed on the size of the single texture, but when I split it into several pieces it still gave the same behavior. I believe this is due to a limitation on the overall viewport size, not the size of individual textures. But my main question is actually concerning the QPainter implementation. The QPainter code seems to work on large image files if you place it inside a QLabel using the paintEvent call, but not in a QGLWidget. I was wondering why this would work in one case and not the other, since it would be preferable for me to stick with the QGLWidget and just have an if case which uses a QPainter inside paintGL() if possible.

here's the QPainter bit. This works if it's inside paintEvent in a QLabel subclass, and does not work if it's inside paintGL() in a QGLWidget subclass. (By works i mean displays large format images, both will display images with dimensions less than 8192 pixels in height and width.)


QPainter p(this);
p.scale(testWidth, testHeight);
QImage* image = new QImage(testWidth, testHeight,
QImage::Format_Indexed8);
image->setNumColors(256);
for(int i = 0; i < 256; i++)
image->setColor(i, QRGB(i,i,i));
//get 8bpp version of test pattern
unsigned char* test8bpp = new unsigned char[testHeight*testWidth];
for(int i = 0; i < (testHeight*testWidth); i++){
test8bpp[i] = testPattern[i] >> 8;
}
for(int i = 0; i < testHeight; i++){
unsigned char* row = image->scanLine(i);
memcpy(row, test8bpp+(i*testWidth), testWidth);
}
p.drawImage(0,0,*image);
delete [] test8bpp;

wysota
30th August 2011, 21:35
Please provide a minimal compilable example reproducing the problem. I think you are incorrectly trying to place a huge GL widget in a small scroll area. Your gfx card probably won't be able to handle that. Instead you should probably subclass QAbstractScrollArea, set a GL viewport for it and adjust your rendering code to include scrollbars positions.

gitarooLegend
31st August 2011, 18:18
Ok, I'll give that a try. I previously was under the impression that subclassing QAbstractScrollArea wouldn't work in the case of a QGLWidget because I needed to handle the draw call directly and I figured that drawing a small enough portion of the frame to fit in the viewport would screw up the ScrollArea's idea of how big the viewport widget was and thus make it assume it didn't need to display the scroll bars because the widget was already small enough. I'll give it a try though and see what happens. If that doesn't work i'll try to provide a more complete source example that you can run and test yourself.

wysota
31st August 2011, 18:22
You need to understand the difference between QScrollArea and QAbstractScrollArea.

gitarooLegend
8th September 2011, 14:58
Thanks wysota! Turns out I was over-thinking things a bit and trying to mess around too much with overriding the setViewport() function and the size related functions of my QGLWidget implementation. Took a bit of tinkering but subclassing QAbstractScrollArea and setting my QGLWidget as a child widget of the viewport solved the problem perfectly.