drawText() on a QImage crashes program - qt

I have an image in uint8_t buffer and I am trying to use QImage as a wrapper to write text on the image. I have used drawLine() with no issues, but drawText() crashes the program. The below code is part of a boost thread in which I want to write text unto each image as it iterates through the function. Are there any bugs in Qt I am unaware of?
uint8_t *frameBuffer; // this contains image pixels
QImage img(frameBuffer, sizeX, m_sizeY, QImage::Format_RGB888);
QPainter p(&img);
p.setPen(QPen(Qt::green));
p.setFont(QFont("Times", 10, QFont::Bold));
p.drawLine(img.rect().bottomLeft().x(), img.rect().bottomLeft().y()-10,
img.rect().bottomRight().x(), img.rect().bottomRight().y()-10); //works!
p.drawText(img.rect(), Qt::AlignCenter, "Help"); //crashes program

My project was set to a QCoreApplication (I had no GUI). Changing it to QApplication did the trick!

Just a guess... (I've never seen this error before, but have had other font issues on threads.)
Font rendering on background threads can be a little flaky in Qt, depending on how it was compiled. Check the value of QFontDatabase::supportsThreadedFontRendering on your system.
Note the documentation:
Returns true if font rendering is supported outside the GUI thread,
false otherwise. In other words, a return value of false means that
all QPainter::drawText() calls outside the GUI thread will not produce
readable output.

Related

OpenGL / QT : Need help in Converting from QImage to Opengl format and render the pixels

We are migrating from Old OpenGl to Modern OpenGL. I am trying to port two functions which uses QT/OpenGL and want to convert to Modern OpenGL. QImage content should be converted to OpenGL Format. Then I want to read the pixels of QImage and render in OpenGL. How to do this in Modern OpenGL. I know glcopypixels() / glDrawPixels() is deprecated. Any pointers? I have the following code but it is in old OpenGL. Basically the whole idea is writing to back buffer and restoring the back buffer and render pixels to avoid redraw. I am using QOpenglWidget Class given by QT Framework (QT 5.1). I have tried many things converting to OpenGL format from QImage. But it did not work. Need your help. Thanks in Advance.
QImage _savedBackBuffer;
void SaveBackBuffer()
{
glReadBuffer(GL_BACK);
QImage buf = this->grabFramebuffer();
_savedBackBuffer = convertToGLFormat(buf); // convertToGLFormat is not available in
QOpenGLWidget class
}
void restoreBackBuffer()
{
glDrawBuffer(GL_BACK);
**glDrawPixels**( _savedBackBuffer.width(), _savedBackBuffer.height(),
GL_RGBA, GL_UNSIGNED_BYTE, _savedBackBuffer.bits() ); ---> glDrawPixels is Deprecated. How to handle this call.
}
flush () {
glReadBuffer (GL_BACK);
glDrawBuffer(GL_FRONT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
**glCopyPixels**(0, 0, _scrWidth, _scrHeight, GL_COLOR); // glCopyPixels Deprecated
...
glflush();
}
I have added below code to grab the Framebuffer. But still I am getting an Empty QImage. Anything wrong with my code.
saveBackBuffer()
{
_bSavingBackBuffer = true;
QString fileName("C:\\Users\\ey617e\\Desktop\\yourFile.png");
QFile file(fileName);
file.open(QIODevice::WriteOnly);
glReadBuffer(GL_BACK);
makeCurrent();
QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
QOpenGLFramebufferObject * fbo = new
QOpenGLFramebufferObject(_scrWidth, _scrHeight, format);
fbo->bind();
paintGL();
_savedBackBuffer = fbo->toImage();
_savedBackBuffer.save(file.fileName(), "PNG");
fbo->release();
}
paintGL()
{
QOpenGLPaintDevice fboPaintDev(_scrWidth, _scrHeight);
QPainter painter(&fboPaintDev);
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
painter.beginNativePainting();
drawDisplayLists(_underIllusDisplayLists);
drawDisplayLists(_illusDisplayLists);
painter.endNativePainting();
painter.drawText(20, 40, "Foo");
painter.end();
}
You can create a QOpenGLTexture object directly from a QImage: https://doc.qt.io/qt-5/qopengltexture.html#QOpenGLTexture-1
You can then use that texture directly for any image related OpenGL operations.
Basically the whole idea is writing to back buffer and restoring the back buffer and render pixels to avoid redraw.
Don't do that! It will actually impair performance, since drawing on top of previously rendered content introduces implicit synchronization points, thereby eliminating options to render new contents in parallel to advancing the presentation swap chain.
As "counterintuitive" as it may sound, just redraw the whole thing, each and every frame. If your codebase is that old, then the complexity of what you're drawing very likely is going to be so low, that you could easily render thousands of frames per second.
On the other hand retaining the contents of the backbuffer constitutes a cache and thus introduces the complexity of deciding upon cache invalidation.
I bet, that just redrawing using modern methods (geometry in buffer objects, index buffers, untangling of sync points) and simplifying the rendering code path by mere elimination the code that's responsible for determining when to actually redraw portions of the picture will actually vastly outperform anything what you had before.

Qt: QOpenGLContexts "mix" with differnt widgets

I am recently building a Qt application with native openGL streaming data in the Qt window.
I have 2 widgets inherited from QOpenGLWidget, one has parent, the other doesn't have a parent. They both work well individually (just show() one widget per time). However, when I try to render them simultaneously, one of the texture I bind via glBindTexture() appears in the wrong window. It's like they are using the same context(). But by inheriting from QOpenGLWidget, they should have two different context.
In my code, I just override initializeGL, paintGL and resizeGL as usual
void initializeGL(){
initializeOpenGLFunctions();
// generate buffer, allocation, shaders...
}
void paintGL(){
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// bind vao, bind texture, drawElements...
}
Basically, my second window(window2) is a "video player" plays the image sequence from memory. But it appears on the window1. I have also set a QSurfaceFormat by:
QSurfaceFormat format;
format.setRenderableType(QSurfaceFormat::OpenGL);
format.setVersion(3,3);
format.setProfile(QSurfaceFormat::CoreProfile);
setFormat(format);
in the constructor.
Could someone tell me what might be wrong here? I think the context() the two windows use are different, then how could I glBindTexture in window2 could apply for window1? If you found these information are not enough, please tell me, Thanks.
platform: Ubuntu16.04, Qt5.6.2, OpenGL3.3
Update:
I have the same issue with this post: OpenGL multiple window rendering. However, mine is inside Qt5 environment, theoretically, it should works.

Qt5.6: high DPI support and OpenGL (OpenSceneGraph)

I have a minimal application which uses QOpenGLWidget that integrates an OpenGL wrapper library (OpenSceneGraph). I am trying to figure out how to correctly use the Qt5.6 support for high DPI screens when dealing with OpenGL content like I use.
My main() function has the following code:
int main(int argc, char** argv)
{
// DPI support is on
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
QMainWindow window;
// QOpenGLWidget with OpenSceneGraph content
QtOSGWidget* widget = new QtOSGWidget();
window.setCentralWidget(widget);
window.show();
return app.exec();
}
The QtOSGWidget is derived from QOpenGLWidget with OpenSceneGraph content: I use osgViewer::GraphicsWindowEmbedded to render my simple scene.
To merge OSG with Qt, I re-define the *GL() methods: paintGL(), resizeGL() and initializeGL(). I follow the Qt docs on what each of the *GL() methods should contain, i.e.:
paintGL() makes sure the viewer is updated
resizeGL() makes sure the graphics window is resized properly (together with camera and viewport);
initializeGL() makes sure OpenGL state is initialized.
I also re-defined Qt mouse events so that to pass the events to OSG
When I run my example on normal resolution screen, or with QApplication::setAttribute(Qt::AA_DisableHighDpiScaling);, the scene looks like it should:
Also, when I manipulate the camera view, the mouse coordinates are captured correctly.
However, when I set the high DPI option on, this is what I get:
The mouse coordinates for events are scaled as well and not passed to the OpenSceneGraph's event handler correctly.
As you can see, the graphics window size is not scaled by Qt. It is probably because of the way how I set up the sizing:
virtual void resizeGL( int width, int height )
{
// resize event is passed to OSG
this->getEventQueue()->windowResize(this->x(), this->y(), width, height);
// graphics window resize
m_graphicsWindow->resized(this->x(), this->y(), width, height);
// camera viewport
osg::Camera* camera = m_viewer->getCamera();
camera->setViewport(0, 0, this->width(), this->height());
}
That sizing is not scaled by Qt. Same thing happens to the mouse events coordinates.
My question: is there a way to know to what size the scaling will be performed so that to do resizeGL() correctly? Or what is the correct way to deal with the problem?
Update/Solution using scaling by hand: thanks to the answer of #AlexanderVX, I figured out the scaling solution. At first, I need to know some reference values of DPI in X and Y dimensions. Then I calculate the scaling coordinates based on that and pass them to my widget QtOSGWidget. So, the code of the main() has to contain:
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
int x = QApplication::desktop()->physicalDpiX();
int y = QApplication::desktop()->physicalDpiY();
// values 284 and 285 are the reference values
double scaleX = 284.0/double(x);
double scaleY = 285.0/double(y);
QMainWindow window;
QtOSGWidget* widget = new QtOSGWidget(scaleX, scaleY, &window);
// etc.
Then, whenever I refer to the sizing functions that needed to be passed to OpenSceneGraph (OpenGL) content, I have to do scaling, e.g.:
// resizeGL example
this->getEventQueue()->windowResize(this->x()*m_scaleX, this->y() * m_scaleY, width*m_scaleX, height*m_scaleY);
// mouse event example
this->getEventQueue()->mouseButtonPress(event->x()*m_scaleX, event->y()*m_scaleY, button);
Final update: since the target platform of my application is Windows 7-10, it makes much more sense to stick with the proposed answer of #AlexanderV (second part), i.e., to use SetProcessDPIAware() function.
Is there a way to know to what size the scaling will be performed so
that to do resizeGL() correctly?
First, detect the monitor:
// relative to widget
int screenNum = QApplication::desktop()->screenNumber(pWidget);
or maybe
// relative to global screen position
int screenNum = QApplication::desktop()->screenNumber(pWidget->topLeft());
and that gives us pointer to QScreen:
QScreen* pScreen = QApplication::desktop()->screen(screenNum);
from which you can read many screen characteristics, including "physical dot per inch" which makes us able to judge how many pixels there per inch:
qreal pxPerInch = pScreen->physicalDotsPerInch();
Having pixels per inch you will be able to programmatically scale your drawing code. Detect how much is 'normal' density and then scale proportionally against the density detected on physical device. Of course that approach is more suitable for accurate graphics. Be aware of both physicalDotPerInch() and devicePixelRatio(), though.
qreal scaleFactor = pScreen->physicalDotsPerInch() / normalPxPerInch;
Or what is the correct way to deal with the problem?
However, with widgets and normal GUI drawing it is often easier to let Qt / system to scale the entire UI. Qt Documentation: High DPI Displays.
If the OS Windows at least Vista or higher and tuning Qt for high DPI sounds complicated then there is a shortcut that I take and it helps me, though Qt complains in the log: "SetProcessDpiAwareness failed: "COM error 0xffffffff80070005 (Unknown error 0x0ffffffff80070005)"
". I call this function from main() before the event loop: SetProcessDPIAware() and then all the UI looks alike no matter what monitor density is. I use it with Qt 5.5, though. There is also SetProcessDpiAwareness() function, explore. I use SetProcessDPIAware because it is available since Windows Vista but SetProcessDpiAwareness is only available since Windows 8.1. So, the decision may depend on potential clients systems.
A 'shortcut' approach:
int main(int argc, char** argv)
{
// DPI support is on
// QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
// on Windows?
::SetProcessDPIAware();
// MSDN suggests not to use SetProcessDPIAware() as it is obsolete and may not be available.
// But it works with widgets.
QApplication app(argc, argv);
QMainWindow window;
// QOpenGLWidget with OpenSceneGraph content
QtOSGWidget* widget = new QtOSGWidget();
window.setCentralWidget(widget);
window.show();
return app.exec();
}

QPixmap memory size depending on fle size

I am creating QGraphicsPixmapItems by loading images from files.
bool PixMapItem::loadItemFromFile(const QString &filename)
{
QPixmap p;
if(!p.load(filename))
return false;
setPixmap(p);
#ifdef NOT_QT_4
m_itemSize = p.size()/p.devicePixelRatio();
#else
m_itemSize = p.size();
#endif
return true;
}
caller:
item->loadItemFromFile(filename);
item->adjustForMaxSize(maxSize);
I would like to know how the memory used by the item depends on the image file size.
1) Type of image - will the encoding of the Pixmap stored in the item be minimal, or always 32 bit, or is there any way to set it ?
2) Assuming the item is resized immediately after load to match a specific size (see above), would the actual image size affect the item memory size ? Would memory size be better if I scale the pixmap instead of the item itself ?
Let me try to answer it, maybe it help you :
I have try long time ago and yes it will always 32 bit, it will be RGB or RGBA (if there is alpha on image, it can be check by hasAlpha). The way you can check is check the depth of the QPixmap which always return 32. Nothing we can do with the pixmap data because it handle internally, which stated on their QPixmap documentation :
Note that the pixel data in a pixmap is internal and is managed by the
underlying window system.
Smaller image will be smaller memory consumed. And you need to free your original pixmap after scale it.
QPixmap is design for painting performance on screen, if you want better handle IO (formating image, decrease the color, etc) use QImage. You also able to render QImage to screen using the painter but it will be slower compare to QPixmap.

Reliably showing a "please wait" dialog while doing a lengthy blocking operation in the main Qt event loop

I've got a Qt app that needs to call an expensive non-Qt function (e.g. to unzip a ~200MB zip file), and since I'm calling that function from the main/GUI thread, the Qt GUI freezes up until the operation completes (i.e. sometimes for 5-10 seconds).
I know that one way to avoid that problem would be to call the expensive function from a separate thread, but since there isn't much the user can do until the unzip completes anyway, that seems like overkill. I can't add processEvents() calls into the expensive function itself, since that function is part of a non-Qt-aware codebase and I don't want to add a Qt dependency to it.
The only thing I want to change, then, is to have a little "Please wait" type message appear during the time that the GUI is blocked, so that the user doesn't think that his mouse click was ignored.
I currently do that like this:
BusySplashWidget * splash = new BusySplashWidget("Please wait…", this);
splash->show();
qApp->processEvents(); // make sure that the splash is actually visible at this point?
ReadGiantZipFile(); // this can take a long time to return
delete splash;
This works 95% of the time, but occasionally the splash widget doesn't appear, or it appears only as a grey rectangle and the "Please wait" text is not visible.
My question is, is there some other call besides qApp->processEvents() that I should also do to guarantee that the splash widget becomes fully visible before the lengthy operation commences? (I suppose I could call qApp->processEvents() over and over again for 100mS, or something, to convince Qt that I'm really serious about this, but I'd like to avoid voodoo-based programming if possible ;))
In case it matters, here is how I implemented my BusySplashWidget constructor:
BusySplashWidget :: BusySplashWidget(const QString & t, QWidget * parent) : QSplashScreen(parent)
{
const int margin = 5;
QFontMetrics fm = fontMetrics();
QRect r(0,0,margin+fm.width(t)+margin, margin+fm.ascent()+fm.descent()+1+margin);
QPixmap pm(r.width(), r.height());
pm.fill(white);
// these braces ensure that ~QPainter() executes before setPixmap()
{
QPainter p(&pm);
p.setPen(black);
p.drawText(r, Qt::AlignCenter, t);
p.drawRect(QRect(0,0,r.width()-1,r.height()-1));
}
setPixmap(pm);
}
Moving to another thread is the correct way to go but for simple operations, there's a much less complicated way to accomplish this without the pain of managing threads.
BusySplashWidget splash("Please wait…", this);
QFutureWatcher<void> watcher;
connect(&watcher, SIGNAL(finished()), &splash, SLOT(quit()));
QFuture<void> future = QtConcurrent::run(ReadGiantZipFile);
watcher.setFuture(future);
splash.exec(); // use exec() instead of show() to open the dialog modally
See the documentation about the QtConcurrent framework for more information.

Resources