Issues with drawing on the HDC of QWidget by functions from other libs.
-
Hi,
I'm very new to Qt and I'm trying to develop a medical image-processing program.The image load and displaying functions are provided by a 3rd-party lib. When I follow the tutorial on its website(https://www.leadtools.com/help/sdk/tutorials/cdll-load-display-and-save-images.html) to load and display images, everything works fine. So I then tried to reimplement its load and display images function on the Qt framework but I failed.
Basically, the load image function is L_LoadBitmap and the displaying function is L_PaintDC. They both return SUCCESS when running but the image on my custom QWidget just failed to appear.
L_PaintDC can display any image, at any size, to any device context(https://www.leadtools.com/help/sdk/main/api/l-paintdc.html). So I think the main task for me is to pass the correct HDC to this function. Since both of the core functions return SUCCESS but the image failed to appear, I guess it's probably because I passed a wrong HDC value to the L_PaintDC() function.
Here are my core codes:
VM_imagewindow* window = new VM_imagewindow(this, fileName); HWND hWnd = HWND((*window).winId()); // Get this window's Handler HDC hdc = GetDC(hWnd); // Get the device context // Define dst rectangle (LEADBmp is the loaded bitmap) RECT rc = { 0, 0, BITMAPWIDTH(&LEADBmp), BITMAPHEIGHT(&LEADBmp) }; int i; // For debug if (LEADBmp.Flags.Allocated) // Paint to DC. L_PaintDC returns 1 (SUCCESS) in this case i = L_PaintDC(hdc, &LEADBmp, NULL, NULL, &rc, NULL, SRCCOPY); window->setWindowFlag(Qt::Window); window->show();And here is the definition of my VM_imagewindow class:
#pragma once #include <QWidget> #include "ui_VM_imagewindow.h" class VM_imagewindow : public QWidget, public Ui::VM_imagewindowClass { Q_OBJECT public: VM_imagewindow(QWidget *parent = nullptr); VM_imagewindow(QWidget* parent, QString winTitle); VM_imagewindow(QWidget* parent, QString winTitle, BITMAPHANDLE bitmap); ~VM_imagewindow(); private: };I'm really not familiar with HDC and HWND such kinds of stuff.
Can anyone find some clues? Thanks very much! -
@Chris-Kawa Thank you a lot for telling me these. That's so enlightening!
I'm a little frustrated knowing these since I'm literally neither an expert on Qt nor programming :( But I hope I can get over these.So in summary of your explanation, from my understanding, is there a way to disable Qt paint function and only apply the drawing function from the foreign API to paint the bitmap on QWidget's DC?
Or as you said, I can create a bitmap to draw to and then wrap it in QImage. I can probably understand the principle but can you provide me more information about how can I create a bitmap and also get its DC to draw my target image using L_PaintDC function? Thank you!
@Yurika said:
is there a way to disable Qt paint function and only apply the drawing function from the foreign API to paint the bitmap on QWidget's DC
You can use some flags when creating your widget, like Qt::WA_NoSystemBackground, to disable redrawing of the widget background, but this is a bit finicky and you'll get in trouble again when mixing this with e.g. child widgets, that draw on top of your widget.
Using a bitmap buffer and drawing it in paint event is a bit more work (not that much), but assures it behaves correctly, as it's just native Qt painting in this case.
As to how to do it - first make some variables in your class:
HDC renderDC; HBITMAP renderBitmap; QImage renderImage;Then in your setup code (e.g. in constructor) create the bitmap, DC for it and the QImage wrapper:
// Create a DC for rendering HDC displayDC = ::GetDC(NULL); renderDC = ::CreateCompatibleDC(displayDC); ::ReleaseDC(NULL, displayDC); // Create a bitmap buffer BITMAPINFO bmi {}; bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = width; bmi.bmiHeader.biHeight = -height; // yup, negative for top-down order bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; uchar* data = nullptr; renderBitmap = ::CreateDIBSection(renderDC, &bmi, DIB_RGB_COLORS, reinterpret_cast<void **>(&data), NULL, 0); // Create a Qt wrapper over the bitmap data renderImage = QImage(data, width, height, QImage::Format_RGB32);Don't forget to clean it up somewhere (e.g. in the destructor):
::DeleteObject(renderBitmap); ::DeleteDC(renderDC);and then you can use it in the paintEvent like this:
HGDIOBJ prevObj = ::SelectObject(renderDC, renderBitmap); // Draw to renderDC using your foreign api here ::SelectObject(renderDC, prevObj); QPainter painter(this); painter.drawImage(0,0, renderImage); -
Hi,
I'm very new to Qt and I'm trying to develop a medical image-processing program.The image load and displaying functions are provided by a 3rd-party lib. When I follow the tutorial on its website(https://www.leadtools.com/help/sdk/tutorials/cdll-load-display-and-save-images.html) to load and display images, everything works fine. So I then tried to reimplement its load and display images function on the Qt framework but I failed.
Basically, the load image function is L_LoadBitmap and the displaying function is L_PaintDC. They both return SUCCESS when running but the image on my custom QWidget just failed to appear.
L_PaintDC can display any image, at any size, to any device context(https://www.leadtools.com/help/sdk/main/api/l-paintdc.html). So I think the main task for me is to pass the correct HDC to this function. Since both of the core functions return SUCCESS but the image failed to appear, I guess it's probably because I passed a wrong HDC value to the L_PaintDC() function.
Here are my core codes:
VM_imagewindow* window = new VM_imagewindow(this, fileName); HWND hWnd = HWND((*window).winId()); // Get this window's Handler HDC hdc = GetDC(hWnd); // Get the device context // Define dst rectangle (LEADBmp is the loaded bitmap) RECT rc = { 0, 0, BITMAPWIDTH(&LEADBmp), BITMAPHEIGHT(&LEADBmp) }; int i; // For debug if (LEADBmp.Flags.Allocated) // Paint to DC. L_PaintDC returns 1 (SUCCESS) in this case i = L_PaintDC(hdc, &LEADBmp, NULL, NULL, &rc, NULL, SRCCOPY); window->setWindowFlag(Qt::Window); window->show();And here is the definition of my VM_imagewindow class:
#pragma once #include <QWidget> #include "ui_VM_imagewindow.h" class VM_imagewindow : public QWidget, public Ui::VM_imagewindowClass { Q_OBJECT public: VM_imagewindow(QWidget *parent = nullptr); VM_imagewindow(QWidget* parent, QString winTitle); VM_imagewindow(QWidget* parent, QString winTitle, BITMAPHANDLE bitmap); ~VM_imagewindow(); private: };I'm really not familiar with HDC and HWND such kinds of stuff.
Can anyone find some clues? Thanks very much! -
@Pl45m4 aYou should paint during QWidget::paintEvent(). Your widget is not even visible in your code above when you're executing L_PaintDC().
-
@Pl45m4 aYou should paint during QWidget::paintEvent(). Your widget is not even visible in your code above when you're executing L_PaintDC().
@Christian-Ehrlicher Why me? :D
It's not my topic :)That would be the next thing I would have replied.
TheHWND/winIDis used before the widget is shown, so it might even change and you're addressing an invalid window -
@Christian-Ehrlicher @Pl45m4 OMG, thanks a lot for informing me of these! I've moved all of the L_PaintDC related codes into the paintEvent(QPaintEvent* e) function and it successfully displays the image.
But there some new issues came out. When I drag or resize the window, the image sometimes flickers and sometimes disappears completely. And when I drag the window again, sometimes the image reappears. These also happen when I restore the window after minimizing it. Here is my new codes:
Claim QPaintEvent override:
protected: void paintEvent(QPaintEvent* e) override;Definition:
void VM_imagewindow::paintEvent(QPaintEvent* e) { HWND hWnd = HWND(winId()); // Get this window's Handler HDC hdc = GetDC(hWnd); // Get the device context RECT rc = { 0, 0, BITMAPWIDTH(&targetBitmap), BITMAPHEIGHT(&targetBitmap) }; // Define dst rectangle int i; // For debug if (targetBitmap.Flags.Allocated) // L_PaintDC returns 1 in this case i = L_PaintDC(hdc, &targetBitmap, NULL, NULL, &rc, NULL, SRCCOPY); }By the way, I'm actually using the QMdiArea and QMdiSubWindow which handles my VM_imagewindow because I want my software to be like photoshop.
Is there anything I'm doing wrong?
-
@Christian-Ehrlicher @Pl45m4 OMG, thanks a lot for informing me of these! I've moved all of the L_PaintDC related codes into the paintEvent(QPaintEvent* e) function and it successfully displays the image.
But there some new issues came out. When I drag or resize the window, the image sometimes flickers and sometimes disappears completely. And when I drag the window again, sometimes the image reappears. These also happen when I restore the window after minimizing it. Here is my new codes:
Claim QPaintEvent override:
protected: void paintEvent(QPaintEvent* e) override;Definition:
void VM_imagewindow::paintEvent(QPaintEvent* e) { HWND hWnd = HWND(winId()); // Get this window's Handler HDC hdc = GetDC(hWnd); // Get the device context RECT rc = { 0, 0, BITMAPWIDTH(&targetBitmap), BITMAPHEIGHT(&targetBitmap) }; // Define dst rectangle int i; // For debug if (targetBitmap.Flags.Allocated) // L_PaintDC returns 1 in this case i = L_PaintDC(hdc, &targetBitmap, NULL, NULL, &rc, NULL, SRCCOPY); }By the way, I'm actually using the QMdiArea and QMdiSubWindow which handles my VM_imagewindow because I want my software to be like photoshop.
Is there anything I'm doing wrong?
@Yurika said in Issues with drawing on the HDC of QWidget by functions from other libs.:
(targetBitmap.Flags.Allocated)
Is this always true after a resize?
A resize/move event should trigger a repaint. -
@Christian-Ehrlicher @Pl45m4 OMG, thanks a lot for informing me of these! I've moved all of the L_PaintDC related codes into the paintEvent(QPaintEvent* e) function and it successfully displays the image.
But there some new issues came out. When I drag or resize the window, the image sometimes flickers and sometimes disappears completely. And when I drag the window again, sometimes the image reappears. These also happen when I restore the window after minimizing it. Here is my new codes:
Claim QPaintEvent override:
protected: void paintEvent(QPaintEvent* e) override;Definition:
void VM_imagewindow::paintEvent(QPaintEvent* e) { HWND hWnd = HWND(winId()); // Get this window's Handler HDC hdc = GetDC(hWnd); // Get the device context RECT rc = { 0, 0, BITMAPWIDTH(&targetBitmap), BITMAPHEIGHT(&targetBitmap) }; // Define dst rectangle int i; // For debug if (targetBitmap.Flags.Allocated) // L_PaintDC returns 1 in this case i = L_PaintDC(hdc, &targetBitmap, NULL, NULL, &rc, NULL, SRCCOPY); }By the way, I'm actually using the QMdiArea and QMdiSubWindow which handles my VM_imagewindow because I want my software to be like photoshop.
Is there anything I'm doing wrong?
@Yurika said in Issues with drawing on the HDC of QWidget by functions from other libs.:
But there some new issues came out. When I drag or resize the window, the image sometimes flickers and sometimes disappears completely. And when I drag the window again, sometimes the image reappears. These also happen when I restore the window after minimizing it.
This sounds like the behavior described in this topic here
@hskoglund 's answer there might work out for you: -
@Yurika said in Issues with drawing on the HDC of QWidget by functions from other libs.:
(targetBitmap.Flags.Allocated)
Is this always true after a resize?
A resize/move event should trigger a repaint.@Christian-Ehrlicher Yes it is. Is repaint also a virtual function defined in QWidget? So did you mean I have to do something inside the repaint function?
-
@Yurika said in Issues with drawing on the HDC of QWidget by functions from other libs.:
But there some new issues came out. When I drag or resize the window, the image sometimes flickers and sometimes disappears completely. And when I drag the window again, sometimes the image reappears. These also happen when I restore the window after minimizing it.
This sounds like the behavior described in this topic here
@hskoglund 's answer there might work out for you: -
The "problem" here is that Qt painting, since 4.something is double buffered, meaning paintEvent does not paint directly onto the window DC, but to a backbuffer that is then BitBlt to the window.
This means that when you create another DC on the same surface you're basically racing with Qt as to who gets to paint last. It might look like something is working, but, as with any race, it's just a coincidence on your particular setup (Windows version, graphics adapter and driver etc. etc.). Might work for some static painting, but the timing changes slightly on resize and you get flicker. On other machine it might flicker in other scenarios, all the time even.
Qt does not expose a window DC that you can draw to, so the only way to do it in sync is to use Qt's drawing facilities i.e. draw using QPainter in paintEvent. Since you have a foreign API for drawing you can create a bitmap to draw to and then wrap it in QImage interface and paint to the window using one of the QPainter's functions. Just make sure that the bitmap and QImage have compatible data formats.
-
The "problem" here is that Qt painting, since 4.something is double buffered, meaning paintEvent does not paint directly onto the window DC, but to a backbuffer that is then BitBlt to the window.
This means that when you create another DC on the same surface you're basically racing with Qt as to who gets to paint last. It might look like something is working, but, as with any race, it's just a coincidence on your particular setup (Windows version, graphics adapter and driver etc. etc.). Might work for some static painting, but the timing changes slightly on resize and you get flicker. On other machine it might flicker in other scenarios, all the time even.
Qt does not expose a window DC that you can draw to, so the only way to do it in sync is to use Qt's drawing facilities i.e. draw using QPainter in paintEvent. Since you have a foreign API for drawing you can create a bitmap to draw to and then wrap it in QImage interface and paint to the window using one of the QPainter's functions. Just make sure that the bitmap and QImage have compatible data formats.
@Chris-Kawa Thank you a lot for telling me these. That's so enlightening!
I'm a little frustrated knowing these since I'm literally neither an expert on Qt nor programming :( But I hope I can get over these.So in summary of your explanation, from my understanding, is there a way to disable Qt paint function and only apply the drawing function from the foreign API to paint the bitmap on QWidget's DC?
Or as you said, I can create a bitmap to draw to and then wrap it in QImage. I can probably understand the principle but can you provide me more information about how can I create a bitmap and also get its DC to draw my target image using L_PaintDC function? Thank you!
-
@Chris-Kawa Thank you a lot for telling me these. That's so enlightening!
I'm a little frustrated knowing these since I'm literally neither an expert on Qt nor programming :( But I hope I can get over these.So in summary of your explanation, from my understanding, is there a way to disable Qt paint function and only apply the drawing function from the foreign API to paint the bitmap on QWidget's DC?
Or as you said, I can create a bitmap to draw to and then wrap it in QImage. I can probably understand the principle but can you provide me more information about how can I create a bitmap and also get its DC to draw my target image using L_PaintDC function? Thank you!
@Yurika said:
is there a way to disable Qt paint function and only apply the drawing function from the foreign API to paint the bitmap on QWidget's DC
You can use some flags when creating your widget, like Qt::WA_NoSystemBackground, to disable redrawing of the widget background, but this is a bit finicky and you'll get in trouble again when mixing this with e.g. child widgets, that draw on top of your widget.
Using a bitmap buffer and drawing it in paint event is a bit more work (not that much), but assures it behaves correctly, as it's just native Qt painting in this case.
As to how to do it - first make some variables in your class:
HDC renderDC; HBITMAP renderBitmap; QImage renderImage;Then in your setup code (e.g. in constructor) create the bitmap, DC for it and the QImage wrapper:
// Create a DC for rendering HDC displayDC = ::GetDC(NULL); renderDC = ::CreateCompatibleDC(displayDC); ::ReleaseDC(NULL, displayDC); // Create a bitmap buffer BITMAPINFO bmi {}; bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = width; bmi.bmiHeader.biHeight = -height; // yup, negative for top-down order bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; uchar* data = nullptr; renderBitmap = ::CreateDIBSection(renderDC, &bmi, DIB_RGB_COLORS, reinterpret_cast<void **>(&data), NULL, 0); // Create a Qt wrapper over the bitmap data renderImage = QImage(data, width, height, QImage::Format_RGB32);Don't forget to clean it up somewhere (e.g. in the destructor):
::DeleteObject(renderBitmap); ::DeleteDC(renderDC);and then you can use it in the paintEvent like this:
HGDIOBJ prevObj = ::SelectObject(renderDC, renderBitmap); // Draw to renderDC using your foreign api here ::SelectObject(renderDC, prevObj); QPainter painter(this); painter.drawImage(0,0, renderImage); -
@Yurika said:
is there a way to disable Qt paint function and only apply the drawing function from the foreign API to paint the bitmap on QWidget's DC
You can use some flags when creating your widget, like Qt::WA_NoSystemBackground, to disable redrawing of the widget background, but this is a bit finicky and you'll get in trouble again when mixing this with e.g. child widgets, that draw on top of your widget.
Using a bitmap buffer and drawing it in paint event is a bit more work (not that much), but assures it behaves correctly, as it's just native Qt painting in this case.
As to how to do it - first make some variables in your class:
HDC renderDC; HBITMAP renderBitmap; QImage renderImage;Then in your setup code (e.g. in constructor) create the bitmap, DC for it and the QImage wrapper:
// Create a DC for rendering HDC displayDC = ::GetDC(NULL); renderDC = ::CreateCompatibleDC(displayDC); ::ReleaseDC(NULL, displayDC); // Create a bitmap buffer BITMAPINFO bmi {}; bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = width; bmi.bmiHeader.biHeight = -height; // yup, negative for top-down order bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; uchar* data = nullptr; renderBitmap = ::CreateDIBSection(renderDC, &bmi, DIB_RGB_COLORS, reinterpret_cast<void **>(&data), NULL, 0); // Create a Qt wrapper over the bitmap data renderImage = QImage(data, width, height, QImage::Format_RGB32);Don't forget to clean it up somewhere (e.g. in the destructor):
::DeleteObject(renderBitmap); ::DeleteDC(renderDC);and then you can use it in the paintEvent like this:
HGDIOBJ prevObj = ::SelectObject(renderDC, renderBitmap); // Draw to renderDC using your foreign api here ::SelectObject(renderDC, prevObj); QPainter painter(this); painter.drawImage(0,0, renderImage);@Chris-Kawa I just tried this and it does work! No flicker anymore and everything seems to be fine! Thank you so much!
Just want to understand the mechanics behind these, can you explain a little bit about why do I have to create the bitmap buffer? What if I don't do this step?
-
Y Yurika has marked this topic as solved on
-
@Chris-Kawa I just tried this and it does work! No flicker anymore and everything seems to be fine! Thank you so much!
Just want to understand the mechanics behind these, can you explain a little bit about why do I have to create the bitmap buffer? What if I don't do this step?
@Yurika Your foreign API draws to a DC and Qt doesn't expose the window DC that it uses to you. The code creates an intermediate buffer and then selects it in a DC that can draw to it. This basically lets your function draw to the buffer. Then a QImage wraps over the same buffer storage so that you can use Qt API (QPainter) to draw to the Qt's backbuffer.
So what happens is:
- Make a custom DC compatible with your display.
- Make a win32 DIB buffer.
- Make a QImage that shares the storage memory with the buffer (this means you can access the same buffer via win32 and Qt APIs).
- Select the buffer into the DC
- Draw to the DC (which effectively means to the buffer)
- Create a QPainter (Qt API that paints to the backbuffer)
- Draw the buffer as QImage to the Qt's backbuffer, which then will be blit to the window when appropriate (usually at monitor's V-Sync).
If your drawing function just paints a picture (HBITMAP) then you can skip the additional buffer and your L_PaintDC function and make the QImage wrapper directly over the source bitmap. The example I posted is just more generic and lets you do any type of painting to the DC.
-
@Yurika Your foreign API draws to a DC and Qt doesn't expose the window DC that it uses to you. The code creates an intermediate buffer and then selects it in a DC that can draw to it. This basically lets your function draw to the buffer. Then a QImage wraps over the same buffer storage so that you can use Qt API (QPainter) to draw to the Qt's backbuffer.
So what happens is:
- Make a custom DC compatible with your display.
- Make a win32 DIB buffer.
- Make a QImage that shares the storage memory with the buffer (this means you can access the same buffer via win32 and Qt APIs).
- Select the buffer into the DC
- Draw to the DC (which effectively means to the buffer)
- Create a QPainter (Qt API that paints to the backbuffer)
- Draw the buffer as QImage to the Qt's backbuffer, which then will be blit to the window when appropriate (usually at monitor's V-Sync).
If your drawing function just paints a picture (HBITMAP) then you can skip the additional buffer and your L_PaintDC function and make the QImage wrapper directly over the source bitmap. The example I posted is just more generic and lets you do any type of painting to the DC.
@Chris-Kawa That makes sense. Thanks very much!