1 | // Copyright (C) 2016 The Qt Company Ltd. |
---|---|
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qmultimediautils_p.h" |
5 | #include "qvideoframe.h" |
6 | #include "qvideoframeformat.h" |
7 | |
8 | #include <QtCore/qdir.h> |
9 | #include <QtCore/qloggingcategory.h> |
10 | |
11 | #include <cmath> |
12 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | static Q_LOGGING_CATEGORY(qLcMultimediaUtils, "qt.multimedia.utils"); |
16 | |
17 | Fraction qRealToFraction(qreal value) |
18 | { |
19 | int integral = int(floor(x: value)); |
20 | value -= qreal(integral); |
21 | if (value == 0.) |
22 | return {.numerator: integral, .denominator: 1}; |
23 | |
24 | const int dMax = 1000; |
25 | int n1 = 0, d1 = 1, n2 = 1, d2 = 1; |
26 | qreal mid = 0.; |
27 | while (d1 <= dMax && d2 <= dMax) { |
28 | mid = qreal(n1 + n2) / (d1 + d2); |
29 | |
30 | if (qAbs(t: value - mid) < 0.000001) { |
31 | break; |
32 | } else if (value > mid) { |
33 | n1 = n1 + n2; |
34 | d1 = d1 + d2; |
35 | } else { |
36 | n2 = n1 + n2; |
37 | d2 = d1 + d2; |
38 | } |
39 | } |
40 | |
41 | if (d1 + d2 <= dMax) |
42 | return {.numerator: n1 + n2 + integral * (d1 + d2), .denominator: d1 + d2}; |
43 | else if (d2 < d1) |
44 | return { .numerator: n2 + integral * d2, .denominator: d2 }; |
45 | else |
46 | return { .numerator: n1 + integral * d1, .denominator: d1 }; |
47 | } |
48 | |
49 | QSize qCalculateFrameSize(QSize resolution, Fraction par) |
50 | { |
51 | if (par.numerator == par.denominator || par.numerator < 1 || par.denominator < 1) |
52 | return resolution; |
53 | |
54 | if (par.numerator > par.denominator) |
55 | return { resolution.width() * par.numerator / par.denominator, resolution.height() }; |
56 | |
57 | return { resolution.width(), resolution.height() * par.denominator / par.numerator }; |
58 | } |
59 | |
60 | QSize qRotatedFrameSize(QSize size, int rotation) |
61 | { |
62 | Q_ASSERT(rotation % 90 == 0); |
63 | return rotation % 180 ? size.transposed() : size; |
64 | } |
65 | |
66 | QSize qRotatedFramePresentationSize(const QVideoFrame &frame) |
67 | { |
68 | // For mirrored frames the rotation can be +/- 180 degrees, |
69 | // but this inaccuracy doesn't impact on the result. |
70 | const int rotation = qToUnderlying(e: frame.rotation()) + qToUnderlying(e: frame.surfaceFormat().rotation()); |
71 | return qRotatedFrameSize(size: frame.size(), rotation); |
72 | } |
73 | |
74 | QUrl qMediaFromUserInput(QUrl url) |
75 | { |
76 | return QUrl::fromUserInput(userInput: url.toString(), workingDirectory: QDir::currentPath(), options: QUrl::AssumeLocalFile); |
77 | } |
78 | |
79 | bool qIsAutoHdrEnabled() |
80 | { |
81 | static const bool autoHdrEnabled = qEnvironmentVariableIntValue(varName: "QT_MEDIA_AUTO_HDR"); |
82 | |
83 | return autoHdrEnabled; |
84 | } |
85 | |
86 | QRhiSwapChain::Format qGetRequiredSwapChainFormat(const QVideoFrameFormat &format) |
87 | { |
88 | constexpr auto sdrMaxLuminance = 100.0f; |
89 | const auto formatMaxLuminance = format.maxLuminance(); |
90 | |
91 | return formatMaxLuminance > sdrMaxLuminance ? QRhiSwapChain::HDRExtendedSrgbLinear |
92 | : QRhiSwapChain::SDR; |
93 | } |
94 | |
95 | bool qShouldUpdateSwapChainFormat(QRhiSwapChain *swapChain, |
96 | QRhiSwapChain::Format requiredSwapChainFormat) |
97 | { |
98 | if (!swapChain) |
99 | return false; |
100 | |
101 | return qIsAutoHdrEnabled() && swapChain->format() != requiredSwapChainFormat |
102 | && swapChain->isFormatSupported(f: requiredSwapChainFormat); |
103 | } |
104 | |
105 | Q_MULTIMEDIA_EXPORT VideoTransformation |
106 | qNormalizedSurfaceTransformation(const QVideoFrameFormat &format) |
107 | { |
108 | VideoTransformation result; |
109 | result.mirrorVertically(mirror: format.scanLineDirection() == QVideoFrameFormat::BottomToTop); |
110 | result.rotate(rotation: format.rotation()); |
111 | result.mirrorHorizontally(mirror: format.isMirrored()); |
112 | return result; |
113 | } |
114 | |
115 | VideoTransformation qNormalizedFrameTransformation(const QVideoFrame &frame, int additionalRotaton) |
116 | { |
117 | VideoTransformation result = qNormalizedSurfaceTransformation(format: frame.surfaceFormat()); |
118 | result.rotate(rotation: frame.rotation()); |
119 | result.mirrorHorizontally(mirror: frame.mirrored()); |
120 | result.rotate(rotation: qVideoRotationFromDegrees(clockwiseDegrees: additionalRotaton)); |
121 | return result; |
122 | } |
123 | |
124 | // Only accepts inputs divisible by 90. |
125 | // Invalid input returns no rotation. |
126 | QtVideo::Rotation qVideoRotationFromDegrees(int clockwiseDegrees) |
127 | { |
128 | if (clockwiseDegrees % 90 != 0) { |
129 | qCWarning(qLcMultimediaUtils) << "qVideoRotationFromAngle(int) received " |
130 | "input not divisible by 90. Input was: " |
131 | << clockwiseDegrees; |
132 | return QtVideo::Rotation::None; |
133 | } |
134 | |
135 | int newDegrees = clockwiseDegrees % 360; |
136 | // Adjust negative rotations into positive ones. |
137 | if (newDegrees < 0) |
138 | newDegrees += 360; |
139 | return static_cast<QtVideo::Rotation>(newDegrees); |
140 | } |
141 | |
142 | VideoTransformationOpt qVideoTransformationFromMatrix(const QTransform &matrix) |
143 | { |
144 | const qreal absScaleX = std::hypot(x: matrix.m11(), y: matrix.m12()); |
145 | const qreal absScaleY = std::hypot(x: matrix.m21(), y: matrix.m22()); |
146 | |
147 | if (qFuzzyIsNull(d: absScaleX) || qFuzzyIsNull(d: absScaleY)) |
148 | return {}; // the matrix is malformed |
149 | |
150 | qreal cos1 = matrix.m11() / absScaleX; |
151 | qreal sin1 = matrix.m12() / absScaleX; |
152 | |
153 | // const: consider yScale > 0, as negative yScale can be compensated via negative xScale |
154 | const qreal sin2 = -matrix.m21() / absScaleY; |
155 | const qreal cos2 = matrix.m22() / absScaleY; |
156 | |
157 | VideoTransformation result; |
158 | |
159 | // try detecting the best pair option to detect mirroring |
160 | |
161 | if (std::abs(x: cos1) + std::abs(x: cos2) > std::abs(x: sin1) + std::abs(x: sin2)) |
162 | result.mirrorredHorizontallyAfterRotation = std::signbit(x: cos1) != std::signbit(x: cos2); |
163 | else |
164 | result.mirrorredHorizontallyAfterRotation = std::signbit(x: sin1) != std::signbit(x: sin2); |
165 | |
166 | if (result.mirrorredHorizontallyAfterRotation) { |
167 | cos1 *= -1; |
168 | sin1 *= -1; |
169 | } |
170 | |
171 | const qreal maxDiscrepancy = 0.2; |
172 | |
173 | if (std::abs(x: cos1 - cos2) > maxDiscrepancy || std::abs(x: sin1 - sin2) > maxDiscrepancy) |
174 | return {}; // the matrix is sheared too much, this is not supported currently |
175 | |
176 | const qreal angle = atan2(y: sin1 + sin2, x: cos1 + cos2); |
177 | Q_ASSERT(!std::isnan(angle)); // checked upon scale validation |
178 | |
179 | result.rotation = qVideoRotationFromDegrees(clockwiseDegrees: qRound(d: angle / M_PI_2) * 90); |
180 | return result; |
181 | } |
182 | |
183 | QT_END_NAMESPACE |
184 |
Definitions
Learn Advanced QML with KDAB
Find out more