/****************************************************************************** QtAV: Multimedia framework based on Qt and FFmpeg Copyright (C) 2012-2022 Wang Bin theoribeiro * This file is part of QtAV (from 2013) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ******************************************************************************/ #include "QmlAV/QQuickItemRenderer.h" #include "QtCore/QCoreApplication" #include #include #include #include #include #include "QtAV/private/mkid.h" #include "QtAV/private/factory.h" #include "QtAV/private/VideoRenderer_p.h" #include "QmlAV/QmlAVPlayer.h" #include "QmlAV/SGVideoNode.h" namespace QtAV { static const VideoRendererId VideoRendererId_QQuickItem = mkid::id32base36_6<'Q','Q','I','t','e','m'>::value; FACTORY_REGISTER(VideoRenderer, QQuickItem, "QQuickItem") class QQuickItemRendererPrivate : public VideoRendererPrivate { public: QQuickItemRendererPrivate(): VideoRendererPrivate() , opengl(true) , frame_changed(false) , fill_mode(QQuickItemRenderer::PreserveAspectFit) , texture(0) , node(0) , source(0) { } virtual ~QQuickItemRendererPrivate() { if (texture) { delete texture; texture = 0; } } virtual void setupQuality() { if (!node) return; if (opengl) return; if (quality == VideoRenderer::QualityFastest) { ((QSGSimpleTextureNode*)node)->setFiltering(QSGTexture::Nearest); } else { ((QSGSimpleTextureNode*)node)->setFiltering(QSGTexture::Linear); } } bool opengl; bool frame_changed; QQuickItemRenderer::FillMode fill_mode; QSGTexture* texture; QSGNode *node; QObject *source; QImage image; QList filters; }; QQuickItemRenderer::QQuickItemRenderer(QQuickItem *parent) : QQuickItem(parent) , VideoRenderer(*new QQuickItemRendererPrivate()) { Q_UNUSED(parent); setFlag(QQuickItem::ItemHasContents, true); connect(this, SIGNAL(windowChanged(QQuickWindow*)), SLOT(handleWindowChange(QQuickWindow*))); } VideoRendererId QQuickItemRenderer::id() const { return VideoRendererId_QQuickItem; } bool QQuickItemRenderer::isSupported(VideoFormat::PixelFormat pixfmt) const { if (pixfmt == VideoFormat::Format_RGB48BE || pixfmt == VideoFormat::Format_Invalid ) return false; if (!isOpenGL()) return VideoFormat::isRGB(pixfmt); // TODO: rectangle texture is not supported (VDA) return OpenGLVideo::isSupported(pixfmt); } bool QQuickItemRenderer::event(QEvent *e) { if (e->type() != QEvent::User) return QQuickItem::event(e); update(); return true; } #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) void QQuickItemRenderer::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) #else void QQuickItemRenderer::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) #endif { #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) QQuickItem::geometryChange(newGeometry, oldGeometry); #else QQuickItem::geometryChanged(newGeometry, oldGeometry); #endif DPTR_D(QQuickItemRenderer); resizeRenderer(newGeometry.size().toSize()); if (d.fill_mode == PreserveAspectCrop) { QSizeF scaled = d.out_rect.size(); scaled.scale(boundingRect().size(), Qt::KeepAspectRatioByExpanding); d.out_rect = QRect(QPoint(), scaled.toSize()); d.out_rect.moveCenter(boundingRect().center().toPoint()); } } bool QQuickItemRenderer::receiveFrame(const VideoFrame &frame) { DPTR_D(QQuickItemRenderer); d.video_frame = frame; if (!isOpenGL()) { d.image = QImage((uchar*)frame.constBits(), frame.width(), frame.height(), frame.bytesPerLine(), frame.imageFormat()); QRect r = realROI(); if (r != QRect(0, 0, frame.width(), frame.height())) d.image = d.image.copy(r); } d.frame_changed = true; // update(); // why update slow? because of calling in a different thread? //QMetaObject::invokeMethod(this, "update"); // slower than directly postEvent QCoreApplication::postEvent(this, new QEvent(QEvent::User)); return true; } QObject* QQuickItemRenderer::source() const { return d_func().source; } void QQuickItemRenderer::setSource(QObject *source) { DPTR_D(QQuickItemRenderer); if (d.source == source) return; d.source = source; Q_EMIT sourceChanged(); if (!source) return; AVPlayer* p = qobject_cast(source); if (!p) { QmlAVPlayer* qp = qobject_cast(source); if (!qp) { qWarning("source MUST be of type AVPlayer or QmlAVPlayer"); return; } p = qp->player(); } p->addVideoRenderer(this); } QQuickItemRenderer::FillMode QQuickItemRenderer::fillMode() const { return d_func().fill_mode; } void QQuickItemRenderer::setFillMode(FillMode mode) { DPTR_D(QQuickItemRenderer); if (d.fill_mode == mode) return; d_func().fill_mode = mode; if (d.fill_mode == Stretch) { setOutAspectRatioMode(RendererAspectRatio); } else {//compute out_rect fits video aspect ratio then compute again if crop setOutAspectRatioMode(VideoAspectRatio); } //m_geometryDirty = true; //update(); Q_EMIT fillModeChanged(mode); } QRectF QQuickItemRenderer::contentRect() const { return videoRect(); } QRectF QQuickItemRenderer::sourceRect() const { return QRectF(QPointF(), videoFrameSize()); } QPointF QQuickItemRenderer::mapPointToItem(const QPointF &point) const { if (videoFrameSize().isEmpty()) return QPointF(); DPTR_D(const QQuickItemRenderer); // Just normalize and use that function // m_nativeSize is transposed in some orientations if (d.rotation()%180 == 0) return mapNormalizedPointToItem(QPointF(point.x() / videoFrameSize().width(), point.y() / videoFrameSize().height())); else return mapNormalizedPointToItem(QPointF(point.x() / videoFrameSize().height(), point.y() / videoFrameSize().width())); } QRectF QQuickItemRenderer::mapRectToItem(const QRectF &rectangle) const { return QRectF(mapPointToItem(rectangle.topLeft()), mapPointToItem(rectangle.bottomRight())).normalized(); } QPointF QQuickItemRenderer::mapNormalizedPointToItem(const QPointF &point) const { qreal dx = point.x(); qreal dy = point.y(); DPTR_D(const QQuickItemRenderer); if (d.rotation()%180 == 0) { dx *= contentRect().width(); dy *= contentRect().height(); } else { dx *= contentRect().height(); dy *= contentRect().width(); } switch (d.rotation()) { case 0: default: return contentRect().topLeft() + QPointF(dx, dy); case 90: return contentRect().bottomLeft() + QPointF(dy, -dx); case 180: return contentRect().bottomRight() + QPointF(-dx, -dy); case 270: return contentRect().topRight() + QPointF(-dy, dx); } } QRectF QQuickItemRenderer::mapNormalizedRectToItem(const QRectF &rectangle) const { return QRectF(mapNormalizedPointToItem(rectangle.topLeft()), mapNormalizedPointToItem(rectangle.bottomRight())).normalized(); } QPointF QQuickItemRenderer::mapPointToSource(const QPointF &point) const { QPointF norm = mapPointToSourceNormalized(point); if (d_func().rotation()%180 == 0) return QPointF(norm.x() * videoFrameSize().width(), norm.y() * videoFrameSize().height()); else return QPointF(norm.x() * videoFrameSize().height(), norm.y() * videoFrameSize().width()); } QRectF QQuickItemRenderer::mapRectToSource(const QRectF &rectangle) const { return QRectF(mapPointToSource(rectangle.topLeft()), mapPointToSource(rectangle.bottomRight())).normalized(); } QPointF QQuickItemRenderer::mapPointToSourceNormalized(const QPointF &point) const { if (contentRect().isEmpty()) return QPointF(); // Normalize the item source point qreal nx = (point.x() - contentRect().x()) / contentRect().width(); qreal ny = (point.y() - contentRect().y()) / contentRect().height(); switch (d_func().rotation()) { case 0: default: return QPointF(nx, ny); case 90: return QPointF(1.0 - ny, nx); case 180: return QPointF(1.0 - nx, 1.0 - ny); case 270: return QPointF(ny, 1.0 - nx); } } QRectF QQuickItemRenderer::mapRectToSourceNormalized(const QRectF &rectangle) const { return QRectF(mapPointToSourceNormalized(rectangle.topLeft()), mapPointToSourceNormalized(rectangle.bottomRight())).normalized(); } bool QQuickItemRenderer::isOpenGL() const { return d_func().opengl; } void QQuickItemRenderer::setOpenGL(bool o) { DPTR_D(QQuickItemRenderer); if (d.opengl == o) return; d.opengl = o; emit openGLChanged(); } QQmlListProperty QQuickItemRenderer::filters() { return QQmlListProperty(this, NULL, vf_append, vf_count, vf_at, vf_clear); } void QQuickItemRenderer::vf_append(QQmlListProperty *property, QuickVideoFilter *value) { QQuickItemRenderer* self = static_cast(property->object); self->d_func().filters.append(value); self->installFilter(value); } int QQuickItemRenderer::vf_count(QQmlListProperty *property) { QQuickItemRenderer* self = static_cast(property->object); return self->d_func().filters.size(); } QuickVideoFilter* QQuickItemRenderer::vf_at(QQmlListProperty *property, int index) { QQuickItemRenderer* self = static_cast(property->object); return self->d_func().filters.at(index); } void QQuickItemRenderer::vf_clear(QQmlListProperty *property) { QQuickItemRenderer* self = static_cast(property->object); foreach (QuickVideoFilter *f, self->d_func().filters) { self->uninstallFilter(f); } self->d_func().filters.clear(); } void QQuickItemRenderer::drawFrame() { DPTR_D(QQuickItemRenderer); if (!d.node) return; if (isOpenGL()) { SGVideoNode *sgvn = static_cast(d.node); Q_ASSERT(sgvn); if (d.frame_changed) sgvn->setCurrentFrame(d.video_frame); d.frame_changed = false; sgvn->setTexturedRectGeometry(d.out_rect, normalizedROI(), d.rotation()); return; } if (!d.frame_changed) { static_cast(d.node)->setRect(d.out_rect); d.node->markDirty(QSGNode::DirtyGeometry); return; } if (d.image.isNull()) { d.image = QImage(rendererSize(), QImage::Format_RGB32); d.image.fill(Qt::black); } static_cast(d.node)->setRect(d.out_rect); if (d.texture) delete d.texture; if (d.rotation() == 0) { d.texture = window()->createTextureFromImage(d.image); } else if (d.rotation() == 180) { d.texture = window()->createTextureFromImage(d.image.mirrored(true, true)); } static_cast(d.node)->setTexture(d.texture); d.node->markDirty(QSGNode::DirtyGeometry); d.frame_changed = false; } QSGNode *QQuickItemRenderer::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) { Q_UNUSED(data); DPTR_D(QQuickItemRenderer); if (d.frame_changed) { if (!node) { if (isOpenGL()) { node = new SGVideoNode(); } else { node = new QSGSimpleTextureNode(); } } } if (!node) { d.frame_changed = false; return 0; } d.node = node; handlePaintEvent(); d.node = 0; return node; } void QQuickItemRenderer::handleWindowChange(QQuickWindow *win) { if (!win) return; connect(win, SIGNAL(beforeRendering()), this, SLOT(beforeRendering()), Qt::DirectConnection); connect(win, SIGNAL(afterRendering()), this, SLOT(afterRendering()), Qt::DirectConnection); } void QQuickItemRenderer::beforeRendering() { d_func().img_mutex.lock(); // TODO: what if current frame not rendered but new frame comes? } void QQuickItemRenderer::afterRendering() { d_func().img_mutex.unlock(); } bool QQuickItemRenderer::onSetOrientation(int value) { Q_UNUSED(value); if (!isOpenGL()) { if (value == 90 || value == 270) return false; } return true; } } // namespace QtAV