Home > front end >  Make QGraphicsVideoItem Fill QWidget
Make QGraphicsVideoItem Fill QWidget

Time:04-11

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)

Screenshot

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?

CodePudding user response:

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.

  • Related