Qt - QPixmap of QLabel don't update after long-running - c++

I use QLabel to show frame captured by camera.
//subThread: capture frame from camera
...
m_videoCapture = new cv::VideoCapture(pipeline, CAP_GSTREAMER);
while(m_videoCapture.isOpened())
{
m_videoCapture->read(m_frame);
if (m_frame)
{
//callback method
m_funcFrame(m_frame, pUser);
}
}
//MainThread
//callback method--show image on QLabel
showImage(cv::Mat& frame)
{
m_image = QImage(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888);
QPixmap pixmap = QPixmap::fromImage(m_image).copy();
m_pixmap = pixmap.scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformat).copy;
ui->m_label->setPixmap(m_pixmap);
}
it work fine but QLabel's pixmap don't update any more on matter how I move camera after 5 or 6 hours. I have to restart my application to let it back to normal.

Related

Make QGraphicsVideoItem Fill QWidget

My goal is to create a simple video player QWidget that allows for some overlayed graphics (think subtitles or similar).
I started with the naive approach, which was to use QVideoWidget with another set of QWidget on top (my overlays). Unfortunately this does not work because Qt does not allow widgets with transparent background to render on top of video. The background shows as black instead of the actual video.
Next idea is to use QGraphicsScene et al. which is supposed to allow this kind of compositing, so I create a dead simple setup like this:
// The widget we will use as viewport
auto viewport= new QWidget();
//Set an easily recognizable bg color for debugging
palette.setColor(QPalette::Window, Qt::green);
viewport->setPalette(palette);
viewport->setAutoFillBackground(true);
// Our scene
auto mScene=new QGraphicsScene(this);
// The video
auto mVideoItem = new QGraphicsVideoItem();
mVideoItem->setPos(0,0);
myVideoSource.setVideoOutput(mVideoItem); // ... not shown: setting up of the video source
mScene->addItem(mVideoItem);
// Yellow line starting at 0,0 for debugging
auto line=new QGraphicsLineItem (0,0,100,100);
line->setPos(0,0);
line->setPen(QPen(Qt::yellow, 2));
mScene->addItem(line);
// A Text string
auto text=new QGraphicsTextItem("Its Wednesday my dudes", mVideoItem);
text->setPos(10, 10);
// Our view
mView=new QGraphicsView;
mView->setScene(mScene);
mView->setViewport(viewport);
viewport->show()
Now this looks promising because I can see compositing works; the line and text render flawlessly on top of the video. However the video is positioned in a seemingly random place in the widget. (see screensdhot)
At this point I have tried every conceivable and inconceivable combination of
mVideoItem->setSize();
mVideoItem->setOffset();
mScene->setSceneRect();
mView->fitInView();
mView->ensureVisible();
mView->centerOn()
Trying to fill the viewport widget with the video item but nothing seems logical at all. Instead of centering the content, it seems to fly around the screen in logic defying ways and I have given up. I put my code in the viewport widget's resizeEvent and use the viewport widget's size() as the base.
So my question is; How can I fill viewport widget with video item on resize?
I dont think QGraphicsVideoItem is good for this task.
You can implement QAbstractVideoSurface that receives QVideoFrames and feeds them to QWidget that converts them to QImage, scales and draws them in paintEvent. Since you control paintEvent you can draw anything over your video, and get "fill viewport" feature for free.
Gist:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Surface surface;
Widget widget(surface);
widget.show();
QMediaPlayer player;
player.setVideoOutput(&surface);
player.setMedia(QMediaContent(QUrl("path/to/media")));
player.play();
return a.exec();
}
bool Surface::present(const QVideoFrame &frame)
{
mFrame = frame;
emit frameReceived();
return true;
}
Widget::Widget(Surface &surface, QWidget *parent)
: QWidget{parent}, mSurface(surface)
{
connect(&mSurface,SIGNAL(frameReceived()),this,SLOT(update()));
}
void Widget::paintEvent(QPaintEvent *event)
{
QVideoFrame frame = mSurface.frame();
if (frame.map(QAbstractVideoBuffer::ReadOnly)) {
QPainter painter(this);
int imageWidth = mSurface.imageSize().width();
int imageHeight = mSurface.imageSize().height();
auto image = QImage(frame.bits(),
imageWidth,
imageHeight,
mSurface.imageFormat());
double scale1 = (double) width() / imageWidth;
double scale2 = (double) height() / imageHeight;
double scale = std::min(scale1, scale2);
QTransform transform;
transform.translate(width() / 2.0, height() / 2.0);
transform.scale(scale, scale);
transform.translate(-imageWidth / 2.0, -imageHeight / 2.0);
painter.setTransform(transform);
painter.drawImage(QPoint(0,0), image);
painter.setTransform(QTransform());
painter.setFont(QFont("Arial", 20));
int fontHeight = painter.fontMetrics().height();
int ypos = height() - (height() - imageHeight * scale) / 2 - fontHeight;
QRectF textRect(QPoint(0, ypos), QSize(width(), fontHeight));
QTextOption opt(Qt::AlignCenter);
painter.setPen(Qt::blue);
painter.drawText(textRect, "Subtitles sample", opt);
frame.unmap();
}
}
Full source: draw-over-video
Based on customvideosurface example from Qt.

how to delay drawing

I want to show the proccess of filling an area line by line. The problem is: even if I create QPixmap from QImage every time I draw a new line (and also delay) and add it to QGraphicsScene in a loop, it doesn't update until the whole thread is finished.
QTest::qWait does the trick. And using QGraphicsPixmapItem for an actual display of the proccess instead of QPixmap itself. I use QPainter initialized from QPixmap and then just reset the pixmap for the pixmap item.
QImage img = QImage(WIDTH, HEIGHT, QImage::Format_RGB32);
img.fill(Qt::white);
QPixmap *pixmapFill = new QPixmap(QPixmap::fromImage(img));
QGraphicsPixmapItem pixmapItem(*pixmapFill);
fillScene->addItem(&pixmapItem);
pixmapItem.setPixmap(*pixmapFill);
// ...
for (auto point : *points) {
QTest::qWait(delay);
painter.drawLine(point.x(), point.y(), xBarrier, point.y());
pixmapItem.setPixmap(*pixmapFill);
}

Is there QLabel::setScaledContents equivalent function for QGraphicsScene in Qt?

I'm using QGraphicsView and QGraphicsScene to display an uploaded image and then show some drawing on it. I'm uploading and image like so:
void MeasuresWidget::on_openAction_triggered()
{
QString fileName = QFileDialog::getOpenFileName(this,tr("Open File"), QDir::currentPath());
if (!fileName.isEmpty())
{
QImage image(fileName);
if (image.isNull())
{
QMessageBox::information(this, tr("Measures Application"), tr("Cannot load %1.").arg(fileName));
return;
}
scene->clear();
scene->addPixmap(QPixmap::fromImage(image).scaledToWidth(w, Qt::SmoothTransformation));
}
}
The problem i'm facing is that if i upload an image that is smaller than the one that was uploaded before, there appears to be empty space, i.e. scene maintains the size of previous image(the bigger one) and is bigger than current one. I tried maintaining original size of scene in individual variable and using setSceneRect() in each upload action:
//in constructor
originalRect = ui->graphicsView->rect();
//in upload action
scene->setSceneRect(originalRect);
but result is that size of scene always stays the same and, if it's bigger than the actual image, cuts it. I used QLabel to display an image before and i used QLabel::setScaledContents() function and it worked fine for me. So, my question is can i achieve the same behaviour with QGraphicsScene?
Update 1:
Application behaves the way i want if i create new scene every upload action. The code now looks like:
void MeasuresWidget::on_openAction_triggered()
{
scene = new QGraphicsScene(this);
ui->graphicsView->setScene(scene);
QString fileName = QFileDialog::getOpenFileName(this,tr("Open File"), QDir::currentPath());
if (!fileName.isEmpty())
{
QImage image(fileName);
if (image.isNull())
{
QMessageBox::information(this, tr("Image Viewer"), tr("Cannot load %1.").arg(fileName));
return;
}
scene->clear();
scene->addPixmap(QPixmap::fromImage(image).scaledToWidth(w, Qt::SmoothTransformation));
}
}
Is this ok? Can i achieve the behaviour i want without a need to create new scene every upload action?
You just have to resize the scene when you insert your pixmap based on its size.
If you define a new class inheriting from QGraphicsScene, you can handle it easily:
class GraphicsScene: public QGraphicsScene
{
public:
GraphicsScene(QRect const& rect, QObject* parent=nullptr): QGraphicsScene(rect, parent),
background(nullptr)
{}
QGraphicsPixmapItem *addPixmap(const QPixmap &pixmap)
{
// We already have a background. Remove it
if (background)
{
removeItem(background);
delete background;
}
background = QGraphicsScene::addPixmap(pixmap);
// Move the pixmap
background->setPos(0, 0);
// Change the scene rect based on the size of the pixmap
setSceneRect(background->boundingRect());
return background;
}
private:
QGraphicsPixmapItem* background;
};
GraphicsScene* scene = new GraphicsScene(QRect());
QGraphicsView* view = new QGraphicsView();
view->setScene(scene);
view->show();
QPixmap pix1(QSize(2000, 2000));
pix1.fill(Qt::red);
QPixmap pix2(QSize(100, 300));
pix2.fill(Qt::green);
// The scene will be 2000x2000
QTimer::singleShot(1000, [=]() { scene->addPixmap(pix1); });
// The scene will be 100x300
QTimer::singleShot(10000, [=]() { scene->addPixmap(pix2); });

How to flip a QImage

What is the right way to flip/mirror a QImage? The following snippet does not work.
//allocate buffer
BYTE* pRgb32Buffer = new BYTE[width*height* 4];
//create paint device
QImage img = QImage(pRgb32Buffer , width, height, getStride(width, pixelFormat), QImage::Format_RGB32);
//do some drawing on image (works!)
QPainter painter(&img);
painter.drawText(10, 50, QString("some text"));
//mirrore image (doesn't mirror the orignal buffer!!!)
img = img.mirrored(false,true);
//doesn't work either
//QImage mirrored = img.mirrored();
//img = mirrored;
//mirrored.detach();
A solution using QImage::mirrored():
memmove(pRgb32Buffer, img.mirrored().bits(),img.byteCount());
I have this working code to mirror a QPixmap onto an QImage.
QPixmap* source = //... Getting my pixmap form somewhere...
QImage target(QSize(source->width(), source->height()), QImage::Format_ARGB32);
QPainter painter(&target);
QTransform transf = painter.transform();
transf.scale(-1, 1);
painter.setTransform(transf);
painter.drawPixmap(-source->width(), 0, *source);
The source contains the mirrored pixmap after that code. You should be able to do the same with QImageand the QPainter::drawImage function as alternative.
Optionally you can save the file if you want (make sure you have the imageformats dll's or it won't write):
QImageWriter writer("c:\\theimage.tiff", "tiff");
writer.setCompression(1);
writer.write(target);

scale a QImage in Qt

What the easiest way to draw an image as a background for a QGraphicsRectItem?
I can set the background as follows but I can't scale the image:
QGraphicsRectItem *enemyItem;
QImage *image = new QImage(":/PaperMario.png");
QBrush *brush = new QBrush(*image);
enemyItem = new QGraphicsRectItem();
enemyItem->setBrush(*brush);
enemyItem->setRect(enemy->getXPos()*30,enemy->getYPos()*30,30,30);
scene->addItem(enemyItem);
inside QGraphicsRectItem
void MySquare::setBrush(QColor _color){
color = _color;
color_pressed = _color;
update(); //repaint
}
I have tried it using:
QBrush *brush = new QBrush(*image->scaled(10));
But I'm stuck: doesn't want to compile.
Is this the way to do it?
Edit:
QImage *image = new QImage(":/PaperMario.png");
QImage *scaled_image = new QImage(image->scaled(35,35,Qt::KeepAspectRatio));
QBrush *brush = new QBrush(*scaled_image);
Does does draw it but it still shows the image multiple times in the drawing
when you display your image in a scene (what i would guess) why dont you use QGraphicsScene::addPixmap(QPixmap) and use the QPixmap::fromImage ( const QImage & image, Qt::ImageConversionFlags flags = Qt::AutoColor ) to get your image into the right format.
*image->scaled(10) does not work on a QImage*. Either you write *image.scaled or (better) image->scaled.