diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/imports/multimedia/qdeclarativevideooutput.cpp | 327 | ||||
-rw-r--r-- | src/imports/multimedia/qdeclarativevideooutput_p.h | 26 |
2 files changed, 331 insertions, 22 deletions
diff --git a/src/imports/multimedia/qdeclarativevideooutput.cpp b/src/imports/multimedia/qdeclarativevideooutput.cpp index a851f3878..feb7bbbf3 100644 --- a/src/imports/multimedia/qdeclarativevideooutput.cpp +++ b/src/imports/multimedia/qdeclarativevideooutput.cpp @@ -164,6 +164,7 @@ QDeclarativeVideoOutput::QDeclarativeVideoOutput(QQuickItem *parent) : QQuickItem(parent), m_sourceType(NoSource), m_fillMode(PreserveAspectFit), + m_geometryDirty(true), m_orientation(0) { setFlag(ItemHasContents, true); @@ -306,6 +307,18 @@ static inline bool qIsDefaultAspect(int o) return (o % 180) == 0; } +/* + * Return the orientation normailized to 0-359 + */ +static inline int qNormalizedOrientation(int o) +{ + // Negative orientations give negative results + int o2 = o % 360; + if (o2 < 0) + o2 += 360; + return o2; +} + /*! \qmlproperty enumeration VideoOutput::fillMode @@ -331,6 +344,7 @@ void QDeclarativeVideoOutput::setFillMode(FillMode mode) return; m_fillMode = mode; + m_geometryDirty = true; update(); emit fillModeChanged(mode); @@ -346,52 +360,87 @@ void QDeclarativeVideoOutput::_q_updateNativeSize(const QVideoSurfaceFormat &for if (m_nativeSize != size) { m_nativeSize = size; + m_geometryDirty = true; + setImplicitWidth(size.width()); setImplicitHeight(size.height()); + + emit sourceRectChanged(); } } +/* Based on fill mode and our size, figure out the source/dest rects */ void QDeclarativeVideoOutput::_q_updateGeometry() { QRectF rect(0, 0, width(), height()); + if (!m_geometryDirty && m_lastSize == rect) + return; + + QRectF oldContentRect(m_contentRect); + + m_geometryDirty = false; + m_lastSize = rect; + if (m_nativeSize.isEmpty()) { //this is necessary for item to receive the //first paint event and configure video surface. - m_boundingRect = rect; - m_sourceRect = QRectF(0, 0, 1, 1); + m_renderedRect = rect; + m_contentRect = rect; + m_sourceTextureRect = QRectF(0, 0, 1, 1); } else if (m_fillMode == Stretch) { - m_boundingRect = rect; - m_sourceRect = QRectF(0, 0, 1, 1); + m_renderedRect = rect; + m_contentRect = rect; + m_sourceTextureRect = QRectF(0, 0, 1, 1); } else if (m_fillMode == PreserveAspectFit) { QSizeF size = m_nativeSize; size.scale(rect.size(), Qt::KeepAspectRatio); - m_boundingRect = QRectF(0, 0, size.width(), size.height()); - m_boundingRect.moveCenter(rect.center()); + m_renderedRect = QRectF(0, 0, size.width(), size.height()); + m_renderedRect.moveCenter(rect.center()); + m_contentRect = m_renderedRect; - m_sourceRect = QRectF(0, 0, 1, 1); + m_sourceTextureRect = QRectF(0, 0, 1, 1); } else if (m_fillMode == PreserveAspectCrop) { - m_boundingRect = rect; + m_renderedRect = rect; - QSizeF size = rect.size(); - size.scale(m_nativeSize, Qt::KeepAspectRatio); + QSizeF scaled = m_nativeSize; + scaled.scale(rect.size(), Qt::KeepAspectRatioByExpanding); - m_sourceRect = QRectF( - 0, 0, size.width() / m_nativeSize.width(), size.height() / m_nativeSize.height()); - m_sourceRect.moveCenter(QPointF(0.5, 0.5)); + m_contentRect = QRectF(QPointF(), scaled); + m_contentRect.moveCenter(rect.center()); + + if (qIsDefaultAspect(m_orientation)) { + m_sourceTextureRect = QRectF((-m_contentRect.left()) / m_contentRect.width(), + (-m_contentRect.top()) / m_contentRect.height(), + rect.width() / m_contentRect.width(), + rect.height() / m_contentRect.height()); + } else { + m_sourceTextureRect = QRectF((-m_contentRect.top()) / m_contentRect.height(), + (-m_contentRect.left()) / m_contentRect.width(), + rect.height() / m_contentRect.height(), + rect.width() / m_contentRect.width()); + } } + + if (m_contentRect != oldContentRect) + emit contentRectChanged(); } /*! \qmlproperty int VideoOutput::orientation - Some sources of video frames have a strict orientation associated with them (for example, - the camera viewfinder), so that rotating the video output (for example via a portrait or - landscape user interface change) should leave the rendered video the same. + In some cases the source video stream requires a certain + orientation to be correct. This includes + sources like a camera viewfinder, where the displayed + viewfinder should match reality, no matter what rotation + the rest of the user interface has. + + This property allows you to apply a rotation (in steps + of 90 degrees) to compensate for any user interface + rotation, with positive values in the anti-clockwise direction. - If you transform this element you may need to apply an adjustment to the - orientation via this property. This value uses degrees as the units, and must be - a multiple of 90 degrees. + The orientation change will also affect the mapping + of coordinates from source to viewport. */ int QDeclarativeVideoOutput::orientation() const { @@ -404,6 +453,10 @@ void QDeclarativeVideoOutput::setOrientation(int orientation) if (orientation % 90) return; + // If there's no actual change, return + if (m_orientation == orientation) + return; + // If the new orientation is the same effect // as the old one, don't update the video node stuff if ((m_orientation % 360) == (orientation % 360)) { @@ -412,6 +465,8 @@ void QDeclarativeVideoOutput::setOrientation(int orientation) return; } + m_geometryDirty = true; + // Otherwise, a new orientation // See if we need to change aspect ratio orientation too bool oldAspect = qIsDefaultAspect(m_orientation); @@ -424,12 +479,244 @@ void QDeclarativeVideoOutput::setOrientation(int orientation) setImplicitWidth(m_nativeSize.width()); setImplicitHeight(m_nativeSize.height()); + + // Source rectangle does not change for orientation } update(); emit orientationChanged(); } +/*! + \qmlproperty rectangle VideoOutput::contentRect + + This property holds the item coordinates of the area that + would contain video to render. With certain fill modes, + this rectangle will be larger than the visible area of this + element. + + This property is useful when other coordinates are specified + in terms of the source dimensions - this applied for relative + (normalized) frame coordinates in the range of 0 to 1.0. + + \sa mapRectToItem(), mapPointToItem() + + Areas outside this will be transparent. +*/ +QRectF QDeclarativeVideoOutput::contentRect() const +{ + return m_contentRect; +} + +/*! + \qmlproperty rectangle VideoOutput::sourceRect + + This property holds the area of the source video + content that is considered for rendering. The + values are in source pixel coordinates. + + Note that typically the top left corner of this rectangle + will be \c {0,0} while the width and height will be the + width and height of the input content. + + The orientation setting does not affect this rectangle. +*/ +QRectF QDeclarativeVideoOutput::sourceRect() const +{ + // We might have to transpose back + QSizeF size = m_nativeSize; + if (!qIsDefaultAspect(m_orientation)) { + size.transpose(); + } + return QRectF(QPointF(), size); // XXX ignores viewport +} + +/*! + \qmlmethod mapNormalizedPointToItem + + Given normalized coordinates \a point (that is, each + component in the range of 0 to 1.0), return the mapped point + that it corresponds to (in item coordinates). + This mapping is affected by the orientation. + + Depending on the fill mode, this point may lie outside the rendered + rectangle. + */ +QPointF QDeclarativeVideoOutput::mapNormalizedPointToItem(const QPointF &point) const +{ + qreal dx = point.x(); + qreal dy = point.y(); + + if (qIsDefaultAspect(m_orientation)) { + dx *= m_contentRect.width(); + dy *= m_contentRect.height(); + } else { + dx *= m_contentRect.height(); + dy *= m_contentRect.width(); + } + + switch (qNormalizedOrientation(m_orientation)) { + case 0: + default: + return m_contentRect.topLeft() + QPointF(dx, dy); + case 90: + return m_contentRect.bottomLeft() + QPointF(dy, -dx); + case 180: + return m_contentRect.bottomRight() + QPointF(-dx, -dy); + case 270: + return m_contentRect.topRight() + QPointF(-dy, dx); + } +} + +/*! + \qmlmethod mapNormalizedRectToItem + + Given a rectangle \a rectangle in normalized + coordinates (that is, each component in the range of 0 to 1.0), + return the mapped rectangle that it corresponds to (in item coordinates). + This mapping is affected by the orientation. + + Depending on the fill mode, this rectangle may extend outside the rendered + rectangle. + */ +QRectF QDeclarativeVideoOutput::mapNormalizedRectToItem(const QRectF &rectangle) const +{ + return QRectF(mapNormalizedPointToItem(rectangle.topLeft()), + mapNormalizedPointToItem(rectangle.bottomRight())).normalized(); +} + +/*! + \qmlmethod mapPointToItem + + Given a point \a point in item coordinates, return the + corresponding point in source coordinates. This mapping is + affected by the orientation. + + If the supplied point lies outside the rendered area, the returned + point will be outside the source rectangle. + */ +QPointF QDeclarativeVideoOutput::mapPointToSource(const QPointF &point) const +{ + QPointF norm = mapPointToSourceNormalized(point); + + if (qIsDefaultAspect(m_orientation)) + return QPointF(norm.x() * m_nativeSize.width(), norm.y() * m_nativeSize.height()); + else + return QPointF(norm.x() * m_nativeSize.height(), norm.y() * m_nativeSize.width()); +} + +/*! + \qmlmethod mapRectToSource + + Given a rectangle \a rectangle in item coordinates, return the + corresponding rectangle in source coordinates. This mapping is + affected by the orientation. + + This mapping is affected by the orientation. + + If the supplied point lies outside the rendered area, the returned + point will be outside the source rectangle. + */ +QRectF QDeclarativeVideoOutput::mapRectToSource(const QRectF &rectangle) const +{ + return QRectF(mapPointToSource(rectangle.topLeft()), + mapPointToSource(rectangle.bottomRight())).normalized(); +} + +/*! + \qmlmethod mapPointToItemNormalized + + Given a point \a point in item coordinates, return the + corresponding point in normalized source coordinates. This mapping is + affected by the orientation. + + If the supplied point lies outside the rendered area, the returned + point will be outside the source rectangle. No clamping is performed. + */ +QPointF QDeclarativeVideoOutput::mapPointToSourceNormalized(const QPointF &point) const +{ + if (m_contentRect.isEmpty()) + return QPointF(); + + // Normalize the item source point + qreal nx = (point.x() - m_contentRect.left()) / m_contentRect.width(); + qreal ny = (point.y() - m_contentRect.top()) / m_contentRect.height(); + + const qreal one(1.0f); + + // For now, the origin of the source rectangle is 0,0 + switch (qNormalizedOrientation(m_orientation)) { + case 0: + default: + return QPointF(nx, ny); + case 90: + return QPointF(one - ny, nx); + case 180: + return QPointF(one - nx, one - ny); + case 270: + return QPointF(ny, one - nx); + } +} + +/*! + \qmlmethod mapRectToSourceNormalized + + Given a rectangle \a rectangle in item coordinates, return the + corresponding rectangle in normalized source coordinates. This mapping is + affected by the orientation. + + This mapping is affected by the orientation. + + If the supplied point lies outside the rendered area, the returned + point will be outside the source rectangle. No clamping is performed. + */ +QRectF QDeclarativeVideoOutput::mapRectToSourceNormalized(const QRectF &rectangle) const +{ + return QRectF(mapPointToSourceNormalized(rectangle.topLeft()), + mapPointToSourceNormalized(rectangle.bottomRight())).normalized(); +} + +/*! + \qmlmethod mapPointToItem + + Given a point \a point in source coordinates, return the + corresponding point in item coordinates. This mapping is + affected by the orientation. + + Depending on the fill mode, this point may lie outside the rendered + rectangle. + */ +QPointF QDeclarativeVideoOutput::mapPointToItem(const QPointF &point) const +{ + if (m_nativeSize.isEmpty()) + return QPointF(); + + // Just normalize and use that function + // m_nativeSize is transposed in some orientations + if (qIsDefaultAspect(m_orientation)) + return mapNormalizedPointToItem(QPointF(point.x() / m_nativeSize.width(), point.y() / m_nativeSize.height())); + else + return mapNormalizedPointToItem(QPointF(point.x() / m_nativeSize.height(), point.y() / m_nativeSize.width())); +} + +/*! + \qmlmethod mapRectToItem + + Given a rectangle \a rectangle in source coordinates, return the + corresponding rectangle in item coordinates. This mapping is + affected by the orientation. + + Depending on the fill mode, this rectangle may extend outside the rendered + rectangle. + + */ +QRectF QDeclarativeVideoOutput::mapRectToItem(const QRectF &rectangle) const +{ + return QRectF(mapPointToItem(rectangle.topLeft()), + mapPointToItem(rectangle.bottomRight())).normalized(); +} + + QSGNode *QDeclarativeVideoOutput::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) { QSGVideoNode *videoNode = static_cast<QSGVideoNode *>(oldNode); @@ -464,7 +751,7 @@ QSGNode *QDeclarativeVideoOutput::updatePaintNode(QSGNode *oldNode, UpdatePaintN _q_updateGeometry(); // Negative rotations need lots of %360 - videoNode->setTexturedRectGeometry(m_boundingRect, m_sourceRect, (360 + (m_orientation % 360)) % 360); + videoNode->setTexturedRectGeometry(m_renderedRect, m_sourceTextureRect, qNormalizedOrientation(m_orientation)); videoNode->setCurrentFrame(m_frame); return videoNode; } diff --git a/src/imports/multimedia/qdeclarativevideooutput_p.h b/src/imports/multimedia/qdeclarativevideooutput_p.h index a06319c46..66f7ca745 100644 --- a/src/imports/multimedia/qdeclarativevideooutput_p.h +++ b/src/imports/multimedia/qdeclarativevideooutput_p.h @@ -42,6 +42,8 @@ #ifndef QDECLARATIVEVIDEOOUTPUT_P_H #define QDECLARATIVEVIDEOOUTPUT_P_H +#include <QtCore/QRectF> + #include <QtQuick/QQuickItem> #include <QtMultimedia/qvideoframe.h> @@ -66,6 +68,8 @@ class QDeclarativeVideoOutput : public QQuickItem Q_PROPERTY(QObject* source READ source WRITE setSource NOTIFY sourceChanged) Q_PROPERTY(FillMode fillMode READ fillMode WRITE setFillMode NOTIFY fillModeChanged) Q_PROPERTY(int orientation READ orientation WRITE setOrientation NOTIFY orientationChanged) + Q_PROPERTY(QRectF sourceRect READ sourceRect NOTIFY sourceRectChanged) + Q_PROPERTY(QRectF contentRect READ contentRect NOTIFY contentRectChanged) Q_ENUMS(FillMode) public: @@ -88,10 +92,24 @@ public: int orientation() const; void setOrientation(int); + QRectF sourceRect() const; + QRectF contentRect() const; + + Q_INVOKABLE QPointF mapPointToItem(const QPointF &point) const; + Q_INVOKABLE QRectF mapRectToItem(const QRectF &rectangle) const; + Q_INVOKABLE QPointF mapNormalizedPointToItem(const QPointF &point) const; + Q_INVOKABLE QRectF mapNormalizedRectToItem(const QRectF &rectangle) const; + Q_INVOKABLE QPointF mapPointToSource(const QPointF &point) const; + Q_INVOKABLE QRectF mapRectToSource(const QRectF &rectangle) const; + Q_INVOKABLE QPointF mapPointToSourceNormalized(const QPointF &point) const; + Q_INVOKABLE QRectF mapRectToSourceNormalized(const QRectF &rectangle) const; + Q_SIGNALS: void sourceChanged(); void fillModeChanged(QDeclarativeVideoOutput::FillMode); void orientationChanged(); + void sourceRectChanged(); + void contentRectChanged(); protected: QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *); @@ -125,8 +143,12 @@ private: QVideoFrame m_frame; FillMode m_fillMode; QSize m_nativeSize; - QRectF m_boundingRect; - QRectF m_sourceRect; + + bool m_geometryDirty; + QRectF m_lastSize; // Cache of last size to avoid recalculating geometry + QRectF m_renderedRect; // Destination pixel coordinates, clipped + QRectF m_contentRect; // Destination pixel coordinates, unclipped + QRectF m_sourceTextureRect; // Source texture coordinates int m_orientation; QMutex m_frameMutex; |