作者: 一去、二三里
个人微信号: iwaleon
微信公众号: 高效程序员
在进行视频或者图像处理时,经常会出现画面分割的场景。
当然了,这里说画面分割是对视频/图像画面的切割,即将同一视频/图像分割成不同的部分,然后进行显示输出,而不是让不同画面显示不同的视频/图像,这样做的好处是每一块视频/图像我们都能单独处理。
一起来看看,今天的视频画面分割器怎么操作吧!
实现细节
为了便于后续使用,我们封装一个网格类 GridView,使其继承自 QGraphicsView:
GridView::GridView(QWidget *parent)
: QGraphicsView(parent)
{
QGraphicsScene *scene = new QGraphicsScene(this);
scene->setBackgroundBrush(QBrush(QColor(Qt::transparent)));
setScene(scene);
fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
}
对于网格来说,由于行和列是不固定的,因此我们需要提供一个可以动态改变行列的接口。
一旦行和列发生变化,就行该立即重置所有的 item:
void GridView::setRowColumn(int rows, int columns)
{
if (rows > 0 && columns > 0) {
m_rowCount = rows;
m_columnCount = columns;
resetItems();
}
}
为了实现重置,我们首先需要删除原有的 item,然后添加新的 item,并将他们布局到当前的行和列中:
void GridView::resetItems()
{
removeItems();
addItems();
layoutItems();
}
以下是具体的删除和新增 item 的逻辑:
void GridView::removeItems()
{
for (QGraphicsItem *item : scene()->items()) {
PixmapItem *pixItem = dynamic_cast<PixmapItem *>(item);
if (pixItem != nullptr) {
scene()->removeItem(item);
delete item;
item = nullptr;
}
}
}
void GridView::addItems()
{
int index = 0;
// 添加 item
for (int i = 0; i < m_rowCount; i++) {
for (int j = 0; j < m_columnCount; j++) {
PixmapItem *item = new PixmapItem(scene());
item->setData(ITEM_INDEX, index);
item->setData(ITEM_ROW, i);
item->setData(ITEM_COLUMN, j);
item->setZValue(0); // 显示在底部
index++;
}
}
}
其实,最重要的是布局的算法,根据场景大小,以及边距、间距等计算各个 item 的位置和大小:
void GridView::layoutItems()
{
// 获取场景大小
QRectF rect = scene()->sceneRect();
// 计算去除边距的大小
qreal leftRectWidth = rect.width()- m_margin * 2;
qreal leftRectHeight = rect.height()- m_margin * 2;
// 计算去除间距的大小
if (m_columnCount > 1)
leftRectWidth -= (m_columnCount - 1) * m_spacing;
if (m_rowCount > 1)
leftRectHeight -= (m_rowCount - 1) * m_spacing;
// 计算每个 item 的大小
qreal itemWidth = leftRectWidth / m_columnCount;
qreal itemHeight = leftRectHeight / m_rowCount;
// 设置每个 item 的位置与大小
for (QGraphicsItem *item : scene()->items()) {
PixmapItem *pixItem = dynamic_cast<PixmapItem *>(item);
if (pixItem != nullptr) {
int row = pixItem->data(ITEM_ROW).toInt();
int column = pixItem->data(ITEM_COLUMN).toInt();
// 根据 item 所在行列计算其坐标值
qreal x = m_margin + column * (itemWidth + m_spacing);
qreal y = m_margin + row * (itemHeight + m_spacing);
pixItem->setGeometry(QRectF(x, y, itemWidth, itemHeight));
}
}
}
随后,就是图片的设置了。当图片改变时,我们也需要更新当前的画面:
void GridView::setPixmap(const QPixmap &pixmap)
{
m_pixmap = pixmap;
updatePixmap();
}
更新图片分为两步,首先是图片的分割,然后是 item 对图片的设置渲染:
void GridView::updatePixmap()
{
QList<QPixmap> pixmaps = splitPixmap(m_pixmap);
// 给每个 item 设置分割后的对应图片
QList<QGraphicsItem *> items = scene()->items();
for (int i = 0; i < items.count(); ++i) {
PixmapItem *pixItem = dynamic_cast<PixmapItem *>(items.at(i));
if (pixItem != nullptr) {
int index = pixItem->data(ITEM_INDEX).toInt();
if (index < pixmaps.count())
pixItem->setPixmap(pixmaps.at(index));
}
}
}
图片分割主要是由 QPixmap::copy() 实现的,而具体切割的位置、大小可以根据行和列来计算:
QList<QPixmap> GridView::splitPixmap(const QPixmap &pixmap)
{
QList<QPixmap> pixmaps;
// 计算分割后图片的大小
int width = pixmap.width() / m_columnCount;
int height = pixmap.height() / m_rowCount;
// 分割原始图片
for (int i = 0; i < m_rowCount; i++) {
for (int j = 0; j < m_columnCount; j++) {
QPixmap pix = pixmap.copy(j * width, i * height, width, height);
pixmaps.append(pix);
}
}
return pixmaps;
}
最后,为了窗口缩放时 item 能够自己动态调整大小,因此需要在 resizeEvent() 中设置场景的大小并重新进行 item 的布局:
void GridView::resizeEvent(QResizeEvent *event)
{
QGraphicsView::resizeEvent(event);
QRectF sceneRect = QRectF(QPointF(0, 0), viewport()->size());
scene()->setSceneRect(sceneRect);
layoutItems();
}
现在,有了 GridView,要进行视频画面的动态分割,简直太容易了。