Add transform/scaling modes to GUI

Added zoom and stretch modes to GUI to mirror the transform modes
available on Android. They are reachable through a context menu or
shortcuts (Ctrl+S/Ctrl+Z).
CLI options --stretch and --zoom have been added as well.

Co-authored-by: Florian Märkl <info@florianmaerkl.de>
This commit is contained in:
Street Pea 2022-12-10 15:09:43 +01:00 committed by Florian Märkl
parent 74d39e6314
commit 801f902bea
9 changed files with 207 additions and 28 deletions

View file

@ -3,6 +3,8 @@
#ifndef CHIAKI_AVOPENGLWIDGET_H
#define CHIAKI_AVOPENGLWIDGET_H
#include "transformmode.h"
#include <chiaki/log.h>
#include <QOpenGLWidget>
@ -74,21 +76,24 @@ class AVOpenGLWidget: public QOpenGLWidget
public:
static QSurfaceFormat CreateSurfaceFormat();
explicit AVOpenGLWidget(StreamSession *session, QWidget *parent = nullptr);
explicit AVOpenGLWidget(StreamSession *session, QWidget *parent = nullptr, TransformMode transform_mode = TransformMode::Fit);
~AVOpenGLWidget() override;
void SwapFrames();
AVOpenGLFrame *GetBackgroundFrame() { return &frames[1 - frame_fg]; }
void SetTransformMode(TransformMode mode) { transform_mode = mode; }
TransformMode GetTransformMode() const { return transform_mode; }
protected:
TransformMode transform_mode;
void mouseMoveEvent(QMouseEvent *event) override;
void initializeGL() override;
void paintGL() override;
private slots:
void ResetMouseTimeout();
public slots:
void ResetMouseTimeout();
void HideMouse();
};

View file

@ -20,6 +20,7 @@
#include "sessionlog.h"
#include "controllermanager.h"
#include "settings.h"
#include "transformmode.h"
#include <QObject>
#include <QImage>
@ -53,9 +54,17 @@ struct StreamSessionConnectInfo
ChiakiConnectVideoProfile video_profile;
unsigned int audio_buffer_size;
bool fullscreen;
TransformMode transform_mode;
bool enable_keyboard;
StreamSessionConnectInfo(Settings *settings, ChiakiTarget target, QString host, QByteArray regist_key, QByteArray morning, bool fullscreen);
StreamSessionConnectInfo(
Settings *settings,
ChiakiTarget target,
QString host,
QByteArray regist_key,
QByteArray morning,
bool fullscreen,
TransformMode transform_mode);
};
class StreamSession : public QObject
@ -124,7 +133,7 @@ class StreamSession : public QObject
#endif
void HandleKeyboardEvent(QKeyEvent *event);
void HandleMouseEvent(QMouseEvent *event);
bool HandleMouseEvent(QMouseEvent *event);
signals:
void FfmpegFrameAvailable();

View file

@ -22,10 +22,14 @@ class StreamWindow: public QMainWindow
const StreamSessionConnectInfo connect_info;
StreamSession *session;
QAction *fullscreen_action;
QAction *stretch_action;
QAction *zoom_action;
AVOpenGLWidget *av_widget;
void Init();
void UpdateVideoTransform();
void UpdateTransformModeActions();
protected:
void keyPressEvent(QKeyEvent *event) override;
@ -42,6 +46,8 @@ class StreamWindow: public QMainWindow
void SessionQuit(ChiakiQuitReason reason, const QString &reason_str);
void LoginPINRequested(bool incorrect);
void ToggleFullscreen();
void ToggleStretch();
void ToggleZoom();
};
#endif // CHIAKI_GUI_STREAMWINDOW_H

View file

@ -0,0 +1,12 @@
// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL
#ifndef CHIAKI_TRANSFORMMODE_H
#define CHIAKI_TRANSFORMMODE_H
enum class TransformMode {
Fit,
Zoom,
Stretch
};
#endif

View file

@ -122,9 +122,9 @@ QSurfaceFormat AVOpenGLWidget::CreateSurfaceFormat()
return format;
}
AVOpenGLWidget::AVOpenGLWidget(StreamSession *session, QWidget *parent)
AVOpenGLWidget::AVOpenGLWidget(StreamSession *session, QWidget *parent, TransformMode transform_mode)
: QOpenGLWidget(parent),
session(session)
session(session), transform_mode(transform_mode)
{
enum AVPixelFormat pixel_format = chiaki_ffmpeg_decoder_get_pixel_format(session->GetFfmpegDecoder());
conversion_config = nullptr;
@ -381,10 +381,10 @@ void AVOpenGLWidget::paintGL()
vp_width = widget_width;
vp_height = widget_height;
}
else
else if(transform_mode == TransformMode::Fit)
{
float aspect = (float)frame->width / (float)frame->height;
if(aspect < (float)widget_width / (float)widget_height)
if(widget_height && aspect < (float)widget_width / (float)widget_height)
{
vp_height = widget_height;
vp_width = (GLsizei)(vp_height * aspect);
@ -395,6 +395,34 @@ void AVOpenGLWidget::paintGL()
vp_height = (GLsizei)(vp_width / aspect);
}
}
else if(transform_mode == TransformMode::Zoom)
{
float aspect = (float)frame->width / (float)frame->height;
if(widget_height && aspect < (float)widget_width / (float)widget_height)
{
vp_width = widget_width;
vp_height = (GLsizei)(vp_width / aspect);
}
else
{
vp_height = widget_height;
vp_width = (GLsizei)(vp_height * aspect);
}
}
else // transform_mode == TransformMode::Stretch
{
float aspect = (float)frame->width / (float)frame->height;
if(widget_height && aspect < (float)widget_width / (float)widget_height)
{
vp_height = widget_height;
vp_width = widget_width;
}
else
{
vp_width = widget_width;
vp_height = widget_height;
}
}
f->glViewport((widget_width - vp_width) / 2, (widget_height - vp_height) / 2, vp_width, vp_height);

View file

@ -103,9 +103,15 @@ int real_main(int argc, char *argv[])
QCommandLineOption morning_option("morning", "", "morning");
parser.addOption(morning_option);
QCommandLineOption fullscreen_option("fullscreen", "Start window in fullscreen (only for use with stream command)");
QCommandLineOption fullscreen_option("fullscreen", "Start window in fullscreen mode [maintains aspect ratio, adds black bars to fill unsused parts of screen if applicable] (only for use with stream command)");
parser.addOption(fullscreen_option);
QCommandLineOption zoom_option("zoom", "Start window in fullscreen zoomed in to fit screen [maintains aspect ratio, cutting off edges of image to fill screen] (only for use with stream command)");
parser.addOption(zoom_option);
QCommandLineOption stretch_option("stretch", "Start window in fullscreen stretched to fit screen [distorts aspect ratio to fill screen] (only for use with stream command)");
parser.addOption(stretch_option);
parser.process(app);
QStringList args = parser.positionalArguments();
@ -174,7 +180,21 @@ int real_main(int argc, char *argv[])
return 1;
}
}
StreamSessionConnectInfo connect_info(&settings, target, host, regist_key, morning, parser.isSet(fullscreen_option));
if ((parser.isSet(stretch_option) && (parser.isSet(zoom_option) || parser.isSet(fullscreen_option))) || (parser.isSet(zoom_option) && parser.isSet(fullscreen_option)))
{
printf("Must choose between fullscreen, zoom or stretch option.");
return 1;
}
StreamSessionConnectInfo connect_info(
&settings,
target,
host,
regist_key,
morning,
parser.isSet(fullscreen_option),
parser.isSet(zoom_option) ? TransformMode::Zoom : parser.isSet(stretch_option) ? TransformMode::Stretch : TransformMode::Fit);
return RunStream(app, connect_info);
}
#ifdef CHIAKI_ENABLE_CLI

View file

@ -249,7 +249,14 @@ void MainWindow::ServerItemWidgetTriggered()
}
QString host = server.GetHostAddr();
StreamSessionConnectInfo info(settings, server.registered_host.GetTarget(), host, server.registered_host.GetRPRegistKey(), server.registered_host.GetRPKey(), false);
StreamSessionConnectInfo info(
settings,
server.registered_host.GetTarget(),
host,
server.registered_host.GetRPRegistKey(),
server.registered_host.GetRPKey(),
false,
TransformMode::Fit);
new StreamWindow(info);
}
else

View file

@ -14,7 +14,14 @@
#define SETSU_UPDATE_INTERVAL_MS 4
StreamSessionConnectInfo::StreamSessionConnectInfo(Settings *settings, ChiakiTarget target, QString host, QByteArray regist_key, QByteArray morning, bool fullscreen)
StreamSessionConnectInfo::StreamSessionConnectInfo(
Settings *settings,
ChiakiTarget target,
QString host,
QByteArray regist_key,
QByteArray morning,
bool fullscreen,
TransformMode transform_mode)
: settings(settings)
{
key_map = settings->GetControllerMappingForDecoding();
@ -30,6 +37,7 @@ StreamSessionConnectInfo::StreamSessionConnectInfo(Settings *settings, ChiakiTar
this->morning = morning;
audio_buffer_size = settings->GetAudioBufferSize();
this->fullscreen = fullscreen;
this->transform_mode = transform_mode;
this->enable_keyboard = false; // TODO: from settings
}
@ -228,13 +236,16 @@ void StreamSession::SetLoginPIN(const QString &pin)
chiaki_session_set_login_pin(&session, (const uint8_t *)data.constData(), data.size());
}
void StreamSession::HandleMouseEvent(QMouseEvent *event)
bool StreamSession::HandleMouseEvent(QMouseEvent *event)
{
if(event->button() != Qt::MouseButton::LeftButton)
return false;
if(event->type() == QEvent::MouseButtonPress)
keyboard_state.buttons |= CHIAKI_CONTROLLER_BUTTON_TOUCHPAD;
else
keyboard_state.buttons &= ~CHIAKI_CONTROLLER_BUTTON_TOUCHPAD;
SendFeedbackState();
return true;
}
void StreamSession::HandleKeyboardEvent(QKeyEvent *event)

View file

@ -10,6 +10,7 @@
#include <QMessageBox>
#include <QCoreApplication>
#include <QAction>
#include <QMenu>
StreamWindow::StreamWindow(const StreamSessionConnectInfo &connect_info, QWidget *parent)
: QMainWindow(parent),
@ -23,8 +24,6 @@ StreamWindow::StreamWindow(const StreamSessionConnectInfo &connect_info, QWidget
try
{
if(connect_info.fullscreen)
showFullScreen();
Init();
}
catch(const Exception &e)
@ -40,6 +39,8 @@ StreamWindow::~StreamWindow()
delete av_widget;
}
#include <QGuiApplication>
void StreamWindow::Init()
{
session = new StreamSession(connect_info, this);
@ -47,10 +48,36 @@ void StreamWindow::Init()
connect(session, &StreamSession::SessionQuit, this, &StreamWindow::SessionQuit);
connect(session, &StreamSession::LoginPINRequested, this, &StreamWindow::LoginPINRequested);
const QKeySequence fullscreen_shortcut = Qt::Key_F11;
const QKeySequence stretch_shortcut = Qt::CTRL + Qt::Key_S;
const QKeySequence zoom_shortcut = Qt::CTRL + Qt::Key_Z;
fullscreen_action = new QAction(tr("Fullscreen"), this);
fullscreen_action->setCheckable(true);
fullscreen_action->setShortcut(fullscreen_shortcut);
addAction(fullscreen_action);
connect(fullscreen_action, &QAction::triggered, this, &StreamWindow::ToggleFullscreen);
if(session->GetFfmpegDecoder())
{
av_widget = new AVOpenGLWidget(session, this);
av_widget = new AVOpenGLWidget(session, this, connect_info.transform_mode);
setCentralWidget(av_widget);
av_widget->setContextMenuPolicy(Qt::CustomContextMenu);
connect(av_widget, &QWidget::customContextMenuRequested, this, [this](const QPoint &pos) {
av_widget->ResetMouseTimeout();
QMenu menu(av_widget);
menu.addAction(fullscreen_action);
menu.addSeparator();
menu.addAction(stretch_action);
menu.addAction(zoom_action);
releaseKeyboard();
connect(&menu, &QMenu::aboutToHide, this, [this] {
grabKeyboard();
});
menu.exec(av_widget->mapToGlobal(pos));
});
}
else
{
@ -63,13 +90,29 @@ void StreamWindow::Init()
session->Start();
auto fullscreen_action = new QAction(tr("Fullscreen"), this);
fullscreen_action->setShortcut(Qt::Key_F11);
addAction(fullscreen_action);
connect(fullscreen_action, &QAction::triggered, this, &StreamWindow::ToggleFullscreen);
stretch_action = new QAction(tr("Stretch"), this);
stretch_action->setCheckable(true);
stretch_action->setShortcut(stretch_shortcut);
addAction(stretch_action);
connect(stretch_action, &QAction::triggered, this, &StreamWindow::ToggleStretch);
zoom_action = new QAction(tr("Zoom"), this);
zoom_action->setCheckable(true);
zoom_action->setShortcut(zoom_shortcut);
addAction(zoom_action);
connect(zoom_action, &QAction::triggered, this, &StreamWindow::ToggleZoom);
resize(connect_info.video_profile.width, connect_info.video_profile.height);
if(connect_info.fullscreen)
{
showFullScreen();
fullscreen_action->setChecked(true);
}
else
show();
UpdateTransformModeActions();
}
void StreamWindow::keyPressEvent(QKeyEvent *event)
@ -86,20 +129,25 @@ void StreamWindow::keyReleaseEvent(QKeyEvent *event)
void StreamWindow::mousePressEvent(QMouseEvent *event)
{
if(session)
session->HandleMouseEvent(event);
if(session && session->HandleMouseEvent(event))
return;
QMainWindow::mousePressEvent(event);
}
void StreamWindow::mouseReleaseEvent(QMouseEvent *event)
{
if(session)
session->HandleMouseEvent(event);
if(session && session->HandleMouseEvent(event))
return;
QMainWindow::mouseReleaseEvent(event);
}
void StreamWindow::mouseDoubleClickEvent(QMouseEvent *event)
{
if(event->button() == Qt::MouseButton::LeftButton)
{
ToggleFullscreen();
return;
}
QMainWindow::mouseDoubleClickEvent(event);
}
@ -175,15 +223,48 @@ void StreamWindow::LoginPINRequested(bool incorrect)
void StreamWindow::ToggleFullscreen()
{
if(isFullScreen())
{
showNormal();
fullscreen_action->setChecked(false);
}
else
{
showFullScreen();
if(av_widget)
av_widget->HideMouse();
fullscreen_action->setChecked(true);
}
}
void StreamWindow::UpdateTransformModeActions()
{
TransformMode tm = av_widget ? av_widget->GetTransformMode() : TransformMode::Fit;
stretch_action->setChecked(tm == TransformMode::Stretch);
zoom_action->setChecked(tm == TransformMode::Zoom);
}
void StreamWindow::ToggleStretch()
{
if(!av_widget)
return;
av_widget->SetTransformMode(
av_widget->GetTransformMode() == TransformMode::Stretch
? TransformMode::Fit
: TransformMode::Stretch);
UpdateTransformModeActions();
}
void StreamWindow::ToggleZoom()
{
if(!av_widget)
return;
av_widget->SetTransformMode(
av_widget->GetTransformMode() == TransformMode::Zoom
? TransformMode::Fit
: TransformMode::Zoom);
UpdateTransformModeActions();
}
void StreamWindow::resizeEvent(QResizeEvent *event)
{
UpdateVideoTransform();