mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-19 15:47:11 +02:00
Animated zoom+rotate in OpenGL media viewer.
This commit is contained in:
parent
5324a626be
commit
23c2bce1bb
8 changed files with 156 additions and 86 deletions
|
@ -451,7 +451,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_settings_system_integration" = "System integration";
|
||||
"lng_settings_performance" = "Performance";
|
||||
"lng_settings_enable_animations" = "Enable animations";
|
||||
"lng_settings_enable_opengl" = "Enable OpenGL rendering for video";
|
||||
"lng_settings_enable_opengl" = "Enable OpenGL rendering for media";
|
||||
"lng_settings_sensitive_title" = "Sensitive content";
|
||||
"lng_settings_sensitive_disable_filtering" = "Disable filtering";
|
||||
"lng_settings_sensitive_about" = "Display sensitive media in public channels on all your Telegram devices.";
|
||||
|
|
|
@ -185,9 +185,6 @@ void OverlayWidget::RendererGL::paintBackground() {
|
|||
? st::mediaviewVideoBg
|
||||
: st::mediaviewBg;
|
||||
auto fill = QRegion(QRect(QPoint(), _viewport));
|
||||
if (_owner->opaqueContentShown()) {
|
||||
fill -= _owner->contentRect();
|
||||
}
|
||||
toggleBlending(false);
|
||||
_background.fill(
|
||||
*_f,
|
||||
|
@ -199,19 +196,14 @@ void OverlayWidget::RendererGL::paintBackground() {
|
|||
}
|
||||
|
||||
void OverlayWidget::RendererGL::paintTransformedVideoFrame(
|
||||
QRect rect,
|
||||
int rotation) {
|
||||
ContentGeometry geometry) {
|
||||
const auto data = _owner->videoFrameWithInfo();
|
||||
if (data.format == Streaming::FrameFormat::None) {
|
||||
return;
|
||||
}
|
||||
if (data.format == Streaming::FrameFormat::ARGB32) {
|
||||
Assert(!data.original.isNull());
|
||||
paintTransformedStaticContent(
|
||||
data.original,
|
||||
rect,
|
||||
rotation,
|
||||
false);
|
||||
paintTransformedStaticContent(data.original, geometry, false);
|
||||
return;
|
||||
}
|
||||
Assert(data.format == Streaming::FrameFormat::YUV420);
|
||||
|
@ -265,13 +257,12 @@ void OverlayWidget::RendererGL::paintTransformedVideoFrame(
|
|||
_yuv420Program->setUniformValue("u_texture", GLint(1));
|
||||
_yuv420Program->setUniformValue("v_texture", GLint(2));
|
||||
|
||||
paintTransformedContent(&*_yuv420Program, rect, rotation);
|
||||
paintTransformedContent(&*_yuv420Program, geometry);
|
||||
}
|
||||
|
||||
void OverlayWidget::RendererGL::paintTransformedStaticContent(
|
||||
const QImage &image,
|
||||
QRect rect,
|
||||
int rotation,
|
||||
ContentGeometry geometry,
|
||||
bool fillTransparentBackground) {
|
||||
auto &program = fillTransparentBackground
|
||||
? _withTransparencyProgram
|
||||
|
@ -307,38 +298,46 @@ void OverlayWidget::RendererGL::paintTransformedStaticContent(
|
|||
_rgbaSize = image.size();
|
||||
}
|
||||
|
||||
paintTransformedContent(&*program, rect, rotation);
|
||||
paintTransformedContent(&*program, geometry);
|
||||
}
|
||||
|
||||
void OverlayWidget::RendererGL::paintTransformedContent(
|
||||
not_null<QOpenGLShaderProgram*> program,
|
||||
QRect rect,
|
||||
int rotation) {
|
||||
ContentGeometry geometry) {
|
||||
auto texCoords = std::array<std::array<GLfloat, 2>, 4> { {
|
||||
{ { 0.f, 1.f } },
|
||||
{ { 1.f, 1.f } },
|
||||
{ { 1.f, 0.f } },
|
||||
{ { 0.f, 0.f } },
|
||||
} };
|
||||
if (const auto shift = (rotation / 90); shift > 0) {
|
||||
std::rotate(
|
||||
texCoords.begin(),
|
||||
texCoords.begin() + shift,
|
||||
texCoords.end());
|
||||
}
|
||||
|
||||
const auto geometry = transformRect(rect);
|
||||
const auto rect = transformRect(geometry.rect);
|
||||
const auto centerx = rect.x() + rect.width() / 2;
|
||||
const auto centery = rect.y() + rect.height() / 2;
|
||||
const auto rsin = std::sinf(geometry.rotation * M_PI / 180.);
|
||||
const auto rcos = std::cosf(geometry.rotation * M_PI / 180.);
|
||||
const auto rotated = [&](float x, float y) -> std::array<float, 2> {
|
||||
x -= centerx;
|
||||
y -= centery;
|
||||
return {
|
||||
centerx + (x * rcos + y * rsin),
|
||||
centery + (y * rcos - x * rsin)
|
||||
};
|
||||
};
|
||||
const auto topleft = rotated(rect.left(), rect.top());
|
||||
const auto topright = rotated(rect.right(), rect.top());
|
||||
const auto bottomright = rotated(rect.right(), rect.bottom());
|
||||
const auto bottomleft = rotated(rect.left(), rect.bottom());
|
||||
const GLfloat coords[] = {
|
||||
geometry.left(), geometry.top(),
|
||||
topleft[0], topleft[1],
|
||||
texCoords[0][0], texCoords[0][1],
|
||||
|
||||
geometry.right(), geometry.top(),
|
||||
topright[0], topright[1],
|
||||
texCoords[1][0], texCoords[1][1],
|
||||
|
||||
geometry.right(), geometry.bottom(),
|
||||
bottomright[0], bottomright[1],
|
||||
texCoords[2][0], texCoords[2][1],
|
||||
|
||||
geometry.left(), geometry.bottom(),
|
||||
bottomleft[0], bottomleft[1],
|
||||
texCoords[3][0], texCoords[3][1],
|
||||
};
|
||||
|
||||
|
@ -661,6 +660,10 @@ Rect OverlayWidget::RendererGL::transformRect(const Rect &raster) const {
|
|||
return TransformRect(raster, _viewport, _factor);
|
||||
}
|
||||
|
||||
Rect OverlayWidget::RendererGL::transformRect(const QRectF &raster) const {
|
||||
return TransformRect(raster, _viewport, _factor);
|
||||
}
|
||||
|
||||
Rect OverlayWidget::RendererGL::transformRect(const QRect &raster) const {
|
||||
return TransformRect(Rect(raster), _viewport, _factor);
|
||||
}
|
||||
|
|
|
@ -47,16 +47,14 @@ private:
|
|||
void setDefaultViewport(QOpenGLFunctions &f);
|
||||
|
||||
void paintBackground();
|
||||
void paintTransformedVideoFrame(QRect rect, int rotation) override;
|
||||
void paintTransformedVideoFrame(ContentGeometry geometry) override;
|
||||
void paintTransformedStaticContent(
|
||||
const QImage &image,
|
||||
QRect rect,
|
||||
int rotation,
|
||||
ContentGeometry geometry,
|
||||
bool fillTransparentBackground) override;
|
||||
void paintTransformedContent(
|
||||
not_null<QOpenGLShaderProgram*> program,
|
||||
QRect rect,
|
||||
int rotation);
|
||||
ContentGeometry geometry);
|
||||
void paintRadialLoading(
|
||||
QRect inner,
|
||||
bool radial,
|
||||
|
@ -90,6 +88,7 @@ private:
|
|||
void toggleBlending(bool enabled);
|
||||
|
||||
[[nodiscard]] Ui::GL::Rect transformRect(const QRect &raster) const;
|
||||
[[nodiscard]] Ui::GL::Rect transformRect(const QRectF &raster) const;
|
||||
[[nodiscard]] Ui::GL::Rect transformRect(
|
||||
const Ui::GL::Rect &raster) const;
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ void OverlayWidget::RendererSW::paintFallback(
|
|||
|
||||
void OverlayWidget::RendererSW::paintBackground() {
|
||||
const auto region = _owner->opaqueContentShown()
|
||||
? (*_clip - _owner->contentRect())
|
||||
? (*_clip - _owner->finalContentRect())
|
||||
: *_clip;
|
||||
|
||||
const auto m = _p->compositionMode();
|
||||
|
@ -44,11 +44,30 @@ void OverlayWidget::RendererSW::paintBackground() {
|
|||
_p->setCompositionMode(m);
|
||||
}
|
||||
|
||||
void OverlayWidget::RendererSW::paintTransformedVideoFrame(
|
||||
QRect rect,
|
||||
QRect OverlayWidget::RendererSW::TransformRect(
|
||||
QRectF geometry,
|
||||
int rotation) {
|
||||
const auto center = geometry.center();
|
||||
const auto rect = ((rotation % 180) == 90)
|
||||
? QRectF(
|
||||
center.x() - geometry.height() / 2.,
|
||||
center.y() - geometry.width() / 2.,
|
||||
geometry.height(),
|
||||
geometry.width())
|
||||
: geometry;
|
||||
return QRect(
|
||||
int(rect.x()),
|
||||
int(rect.y()),
|
||||
int(rect.width()),
|
||||
int(rect.height()));
|
||||
}
|
||||
|
||||
void OverlayWidget::RendererSW::paintTransformedVideoFrame(
|
||||
ContentGeometry geometry) {
|
||||
Expects(_owner->_streamed != nullptr);
|
||||
|
||||
const auto rotation = int(geometry.rotation);
|
||||
const auto rect = TransformRect(geometry.rect, rotation);
|
||||
if (!rect.intersects(_clipOuter)) {
|
||||
return;
|
||||
}
|
||||
|
@ -57,9 +76,10 @@ void OverlayWidget::RendererSW::paintTransformedVideoFrame(
|
|||
|
||||
void OverlayWidget::RendererSW::paintTransformedStaticContent(
|
||||
const QImage &image,
|
||||
QRect rect,
|
||||
int rotation,
|
||||
ContentGeometry geometry,
|
||||
bool fillTransparentBackground) {
|
||||
const auto rotation = int(geometry.rotation);
|
||||
const auto rect = TransformRect(geometry.rect, rotation);
|
||||
if (!rect.intersects(_clipOuter)) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -23,11 +23,10 @@ public:
|
|||
|
||||
private:
|
||||
void paintBackground() override;
|
||||
void paintTransformedVideoFrame(QRect rect, int rotation) override;
|
||||
void paintTransformedVideoFrame(ContentGeometry geometry) override;
|
||||
void paintTransformedStaticContent(
|
||||
const QImage &image,
|
||||
QRect rect,
|
||||
int rotation,
|
||||
ContentGeometry geometry,
|
||||
bool fillTransparentBackground) override;
|
||||
void paintTransformedImage(
|
||||
const QImage &image,
|
||||
|
@ -54,6 +53,8 @@ private:
|
|||
|
||||
void invalidate() override;
|
||||
|
||||
[[nodiscard]] static QRect TransformRect(QRectF geometry, int rotation);
|
||||
|
||||
const not_null<OverlayWidget*> _owner;
|
||||
QBrush _transparentBrush;
|
||||
|
||||
|
|
|
@ -14,11 +14,10 @@ namespace Media::View {
|
|||
class OverlayWidget::Renderer : public Ui::GL::Renderer {
|
||||
public:
|
||||
virtual void paintBackground() = 0;
|
||||
virtual void paintTransformedVideoFrame(QRect rect, int rotation) = 0;
|
||||
virtual void paintTransformedVideoFrame(ContentGeometry geometry) = 0;
|
||||
virtual void paintTransformedStaticContent(
|
||||
const QImage &image,
|
||||
QRect rect,
|
||||
int rotation,
|
||||
ContentGeometry geometry,
|
||||
bool fillTransparentBackground) = 0;
|
||||
virtual void paintRadialLoading(
|
||||
QRect inner,
|
||||
|
|
|
@ -963,23 +963,62 @@ void OverlayWidget::updateCursor() {
|
|||
: (_over == OverNone ? style::cur_default : style::cur_pointer));
|
||||
}
|
||||
|
||||
int OverlayWidget::contentRotation() const {
|
||||
if (!_streamed) {
|
||||
return _rotation;
|
||||
}
|
||||
return (_rotation + (_streamed
|
||||
? _streamed->instance.info().video.rotation
|
||||
: 0)) % 360;
|
||||
int OverlayWidget::finalContentRotation() const {
|
||||
return _streamed
|
||||
? ((_rotation + (_streamed
|
||||
? _streamed->instance.info().video.rotation
|
||||
: 0)) % 360)
|
||||
: _rotation;
|
||||
}
|
||||
|
||||
QRect OverlayWidget::contentRect() const {
|
||||
const auto progress = _zoomAnimation.value(1.);
|
||||
return {
|
||||
anim::interpolate(_xOld, _x, progress),
|
||||
anim::interpolate(_yOld, _y, progress),
|
||||
anim::interpolate(_wOld, _w, progress),
|
||||
anim::interpolate(_hOld, _h, progress),
|
||||
};
|
||||
QRect OverlayWidget::finalContentRect() const {
|
||||
return { _x, _y, _w, _h };
|
||||
}
|
||||
|
||||
OverlayWidget::ContentGeometry OverlayWidget::contentGeometry() const {
|
||||
const auto toRotation = qreal(finalContentRotation());
|
||||
const auto toRectRotated = QRectF(finalContentRect());
|
||||
const auto toRectCenter = toRectRotated.center();
|
||||
const auto toRect = ((int(toRotation) % 180) == 90)
|
||||
? QRectF(
|
||||
toRectCenter.x() - toRectRotated.height() / 2.,
|
||||
toRectCenter.y() - toRectRotated.width() / 2.,
|
||||
toRectRotated.height(),
|
||||
toRectRotated.width())
|
||||
: toRectRotated;
|
||||
if (!_geometryAnimation.animating()) {
|
||||
return { toRect, toRotation };
|
||||
}
|
||||
const auto fromRect = _oldGeometry.rect;
|
||||
const auto fromRotation = _oldGeometry.rotation;
|
||||
const auto progress = _geometryAnimation.value(1.);
|
||||
const auto rotationDelta = (toRotation - fromRotation);
|
||||
const auto useRotationDelta = (rotationDelta > 180.)
|
||||
? (rotationDelta - 360.)
|
||||
: (rotationDelta <= -180.)
|
||||
? (rotationDelta + 360.)
|
||||
: rotationDelta;
|
||||
const auto rotation = fromRotation + useRotationDelta * progress;
|
||||
const auto useRotation = (rotation > 360.)
|
||||
? (rotation - 360.)
|
||||
: (rotation < 0.)
|
||||
? (rotation + 360.)
|
||||
: rotation;
|
||||
const auto useRect = QRectF(
|
||||
fromRect.x() + (toRect.x() - fromRect.x()) * progress,
|
||||
fromRect.y() + (toRect.y() - fromRect.y()) * progress,
|
||||
fromRect.width() + (toRect.width() - fromRect.width()) * progress,
|
||||
fromRect.height() + (toRect.height() - fromRect.height()) * progress
|
||||
);
|
||||
return { useRect, useRotation };
|
||||
}
|
||||
|
||||
void OverlayWidget::updateContentRect() {
|
||||
if (_opengl) {
|
||||
update();
|
||||
} else {
|
||||
update(finalContentRect());
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayWidget::contentSizeChanged() {
|
||||
|
@ -1035,7 +1074,7 @@ void OverlayWidget::resizeContentByScreenSize() {
|
|||
}
|
||||
_x = (width() - _w) / 2;
|
||||
_y = (height() - _h) / 2;
|
||||
_zoomAnimation.stop();
|
||||
_geometryAnimation.stop();
|
||||
}
|
||||
|
||||
float64 OverlayWidget::radialProgress() const {
|
||||
|
@ -2612,13 +2651,13 @@ void OverlayWidget::streamingReady(Streaming::Information &&info) {
|
|||
if (videoShown()) {
|
||||
applyVideoSize();
|
||||
}
|
||||
update(contentRect());
|
||||
updateContentRect();
|
||||
}
|
||||
|
||||
void OverlayWidget::applyVideoSize() {
|
||||
const auto contentSize = style::ConvertScale(videoSize());
|
||||
if (contentSize != QSize(_width, _height)) {
|
||||
update(contentRect());
|
||||
updateContentRect();
|
||||
_w = contentSize.width();
|
||||
_h = contentSize.height();
|
||||
contentSizeChanged();
|
||||
|
@ -2668,7 +2707,7 @@ bool OverlayWidget::createStreamingObjects() {
|
|||
QImage OverlayWidget::transformedShownContent() const {
|
||||
return transformShownContent(
|
||||
videoShown() ? currentVideoFrameImage() : _staticContent,
|
||||
contentRotation());
|
||||
finalContentRotation());
|
||||
}
|
||||
|
||||
QImage OverlayWidget::transformShownContent(
|
||||
|
@ -2697,7 +2736,7 @@ void OverlayWidget::handleStreamingUpdate(Streaming::Update &&update) {
|
|||
}, [&](const PreloadedVideo &update) {
|
||||
updatePlaybackState();
|
||||
}, [&](const UpdateVideo &update) {
|
||||
this->update(contentRect());
|
||||
updateContentRect();
|
||||
Core::App().updateNonIdle();
|
||||
updatePlaybackState();
|
||||
}, [&](const PreloadedAudio &update) {
|
||||
|
@ -2874,6 +2913,8 @@ void OverlayWidget::playbackControlsToPictureInPicture() {
|
|||
}
|
||||
|
||||
void OverlayWidget::playbackControlsRotate() {
|
||||
_oldGeometry = contentGeometry();
|
||||
_geometryAnimation.stop();
|
||||
if (_photo) {
|
||||
auto &storage = _photo->owner().mediaRotation();
|
||||
storage.set(_photo, storage.get(_photo) - 90);
|
||||
|
@ -2885,11 +2926,19 @@ void OverlayWidget::playbackControlsRotate() {
|
|||
_rotation = storage.get(_document);
|
||||
if (videoShown()) {
|
||||
applyVideoSize();
|
||||
update(contentRect());
|
||||
updateContentRect();
|
||||
} else {
|
||||
redisplayContent();
|
||||
}
|
||||
}
|
||||
if (_opengl) {
|
||||
_geometryAnimation.start(
|
||||
[=] { update(); },
|
||||
0.,
|
||||
1.,
|
||||
st::widgetFadeDuration/*,
|
||||
st::easeOutCirc*/);
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayWidget::playbackPauseResume() {
|
||||
|
@ -2923,7 +2972,7 @@ void OverlayWidget::restartAtSeekPosition(crl::time position) {
|
|||
const auto saved = base::take(_rotation);
|
||||
_staticContent = transformedShownContent();
|
||||
_rotation = saved;
|
||||
update(contentRect());
|
||||
updateContentRect();
|
||||
}
|
||||
auto options = Streaming::PlaybackOptions();
|
||||
options.position = position;
|
||||
|
@ -3168,10 +3217,8 @@ Ui::GL::ChosenRenderer OverlayWidget::chooseRenderer(
|
|||
void OverlayWidget::paint(not_null<Renderer*> renderer) {
|
||||
renderer->paintBackground();
|
||||
if (contentShown()) {
|
||||
const auto rect = contentRect();
|
||||
const auto rotation = contentRotation();
|
||||
if (videoShown()) {
|
||||
renderer->paintTransformedVideoFrame(rect, rotation);
|
||||
renderer->paintTransformedVideoFrame(contentGeometry());
|
||||
if (_streamed->instance.player().ready()) {
|
||||
_streamed->instance.markFrameShown();
|
||||
}
|
||||
|
@ -3183,8 +3230,7 @@ void OverlayWidget::paint(not_null<Renderer*> renderer) {
|
|||
|| _staticContent.hasAlphaChannel());
|
||||
renderer->paintTransformedStaticContent(
|
||||
_staticContent,
|
||||
rect,
|
||||
rotation,
|
||||
contentGeometry(),
|
||||
fillTransparentBackground);
|
||||
}
|
||||
paintRadialLoading(renderer);
|
||||
|
@ -3736,11 +3782,8 @@ void OverlayWidget::setZoomLevel(int newZoom, bool force) {
|
|||
const auto contentSize = videoShown()
|
||||
? style::ConvertScale(videoSize())
|
||||
: QSize(_width, _height);
|
||||
const auto current = contentRect();
|
||||
_xOld = current.x();
|
||||
_yOld = current.y();
|
||||
_wOld = current.width();
|
||||
_hOld = current.height();
|
||||
_oldGeometry = contentGeometry();
|
||||
_geometryAnimation.stop();
|
||||
|
||||
_w = contentSize.width();
|
||||
_h = contentSize.height();
|
||||
|
@ -3764,15 +3807,14 @@ void OverlayWidget::setZoomLevel(int newZoom, bool force) {
|
|||
_x = qRound(nx / (-z + 1) + width() / 2.);
|
||||
_y = qRound(ny / (-z + 1) + height() / 2.);
|
||||
}
|
||||
_zoomAnimation.stop();
|
||||
snapXY();
|
||||
if (_opengl) {
|
||||
_zoomAnimation.start(
|
||||
_geometryAnimation.start(
|
||||
[=] { update(); },
|
||||
0.,
|
||||
1.,
|
||||
st::widgetFadeDuration,
|
||||
anim::easeOutCirc);
|
||||
st::widgetFadeDuration/*,
|
||||
anim::easeOutCirc*/);
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
@ -4197,7 +4239,7 @@ void OverlayWidget::updateOver(QPoint pos) {
|
|||
updateOverState(OverMore);
|
||||
} else if (_closeNav.contains(pos)) {
|
||||
updateOverState(OverClose);
|
||||
} else if (documentContentShown() && contentRect().contains(pos)) {
|
||||
} else if (documentContentShown() && finalContentRect().contains(pos)) {
|
||||
if ((_document->isVideoFile() || _document->isVideoMessage()) && _streamed) {
|
||||
updateOverState(OverVideo);
|
||||
} else if (!_streamed && !_documentMedia->loaded()) {
|
||||
|
|
|
@ -143,6 +143,10 @@ private:
|
|||
QuickSave,
|
||||
SaveAs,
|
||||
};
|
||||
struct ContentGeometry {
|
||||
QRectF rect;
|
||||
qreal rotation = 0.;
|
||||
};
|
||||
|
||||
[[nodiscard]] not_null<QWindow*> window() const;
|
||||
[[nodiscard]] int width() const;
|
||||
|
@ -322,8 +326,10 @@ private:
|
|||
void documentUpdated(DocumentData *doc);
|
||||
void changingMsgId(not_null<HistoryItem*> row, MsgId oldId);
|
||||
|
||||
[[nodiscard]] int contentRotation() const;
|
||||
[[nodiscard]] QRect contentRect() const;
|
||||
[[nodiscard]] int finalContentRotation() const;
|
||||
[[nodiscard]] QRect finalContentRect() const;
|
||||
[[nodiscard]] ContentGeometry contentGeometry() const;
|
||||
void updateContentRect();
|
||||
void contentSizeChanged();
|
||||
|
||||
// Radial animation interface.
|
||||
|
@ -467,14 +473,14 @@ private:
|
|||
int _zoom = 0; // < 0 - out, 0 - none, > 0 - in
|
||||
float64 _zoomToScreen = 0.; // for documents
|
||||
float64 _zoomToDefault = 0.;
|
||||
int _xOld = 0, _yOld = 0, _wOld = 0, _hOld = 0;
|
||||
Ui::Animations::Simple _zoomAnimation;
|
||||
QPoint _mStart;
|
||||
bool _pressed = false;
|
||||
int32 _dragging = 0;
|
||||
QImage _staticContent;
|
||||
bool _blurred = true;
|
||||
|
||||
ContentGeometry _oldGeometry;
|
||||
Ui::Animations::Simple _geometryAnimation;
|
||||
rpl::lifetime _screenGeometryLifetime;
|
||||
std::unique_ptr<QObject> _applicationEventFilter;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue