diff --git a/cameracontrolwidget.cpp b/cameracontrolwidget.cpp index 5bd951f..a0d1bb6 100644 --- a/cameracontrolwidget.cpp +++ b/cameracontrolwidget.cpp @@ -31,6 +31,7 @@ void CameraControlWidget::setupUI() scrollWidget->setLayout(scrollLayout); scrollArea->setWidget(scrollWidget); + scrollWidget->setMaximumWidth(500); mainLayout->addWidget(scrollArea); @@ -48,6 +49,8 @@ QGroupBox* CameraControlWidget::createFormatGroup() m_formatCombo = new QComboBox(this); m_formatCombo->addItem("1280x720@30fps UYVY (Supported)", "1280,720,30,UYVY"); + m_formatCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); + m_formatCombo->setMinimumContentsLength(20); m_getFormatsBtn = new QPushButton("Get Available Formats", this); m_setFormatBtn = new QPushButton("Set Format", this); @@ -221,7 +224,7 @@ void CameraControlWidget::onGetFormats() QString format = fmt["format"].toString(); QString displayText = QString("%1x%2@%3fps %4").arg(width, height, fps).arg(format); - QString data = QString("%1,%2,%3,%4").arg(width, height, fps).arg(format); + QString data = QString("%1,%2,%3,%4").arg(width).arg(height).arg(fps).arg(format); m_formatCombo->addItem(displayText, data); } diff --git a/gstreamerpipelinewidget.cpp b/gstreamerpipelinewidget.cpp index 6829971..6cfce85 100644 --- a/gstreamerpipelinewidget.cpp +++ b/gstreamerpipelinewidget.cpp @@ -41,6 +41,8 @@ void GStreamerPipelineWidget::setupUI() auto* formatLabel = new QLabel("Video Format:", this); m_formatCombo = new QComboBox(this); m_formatCombo->addItem("1280x720@30fps UYVY (Default/Supported)", "1280,720,30,UYVY"); + m_formatCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); + m_formatCombo->setMinimumContentsLength(20); groupLayout->addWidget(formatLabel); groupLayout->addWidget(m_formatCombo); @@ -54,6 +56,8 @@ void GStreamerPipelineWidget::setupUI() m_pipelinePresets->addItem("TCP H.264 Stream", "videoconvert ! x264enc ! h264parse ! mpegtsmux ! tcpserversink host=0.0.0.0 port=5000"); m_pipelinePresets->addItem("MJPEG HTTP Stream", "videoconvert ! jpegenc ! multipartmux ! tcpserversink host=0.0.0.0 port=8080"); m_pipelinePresets->addItem("Save to File", "videoconvert ! x264enc ! mp4mux ! filesink location=/tmp/output.mp4"); + m_pipelinePresets->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); + m_pipelinePresets->setMinimumContentsLength(25); connect(m_pipelinePresets, QOverload::of(&QComboBox::currentIndexChanged), this, &GStreamerPipelineWidget::onPipelinePresetChanged); @@ -91,6 +95,8 @@ void GStreamerPipelineWidget::setupUI() m_statusLabel->setStyleSheet("QLabel { background-color: #f0f0f0; padding: 5px; border-radius: 3px; }"); groupLayout->addWidget(m_statusLabel); + groupBox->setMaximumWidth(500); + mainLayout->addWidget(groupBox); mainLayout->addStretch(); @@ -305,7 +311,7 @@ void GStreamerPipelineWidget::onFormatsReceived(const QJsonObject& response) QString format = fmt["format"].toString(); QString displayText = QString("%1x%2@%3fps %4").arg(width, height, fps).arg(format); - QString data = QString("%1,%2,%3,%4").arg(width, height, fps).arg(format); + QString data = QString("%1,%2,%3,%4").arg(width).arg(height).arg(fps).arg(format); m_formatCombo->addItem(displayText, data); } diff --git a/videoviewerwidget.cpp b/videoviewerwidget.cpp index 33d97f4..56bf8ab 100644 --- a/videoviewerwidget.cpp +++ b/videoviewerwidget.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include VideoViewerWidget::VideoViewerWidget(QWidget *parent) @@ -19,10 +20,16 @@ VideoViewerWidget::VideoViewerWidget(QWidget *parent) m_statusLabel(nullptr), m_zoomSlider(nullptr), m_zoomLabel(nullptr), + m_rotationCombo(nullptr), + m_flipHorizontal(nullptr), + m_flipVertical(nullptr), m_pipeline(nullptr), m_appSink(nullptr), m_zoomFactor(1.0), - m_busWatchId(0) + m_busWatchId(0), + m_rotationAngle(0), + m_flipHorizontalEnabled(false), + m_flipVerticalEnabled(false) { initGStreamer(); setupUI(); @@ -77,6 +84,30 @@ void VideoViewerWidget::setupUI() zoomLayout->addWidget(m_zoomSlider); zoomLayout->addWidget(m_zoomLabel); + auto* transformLayout = new QHBoxLayout(); + transformLayout->addWidget(new QLabel("Rotation:", this)); + + m_rotationCombo = new QComboBox(this); + m_rotationCombo->addItem("0°", 0); + m_rotationCombo->addItem("90°", 90); + m_rotationCombo->addItem("180°", 180); + m_rotationCombo->addItem("270°", 270); + m_rotationCombo->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + m_rotationCombo->setMinimumWidth(70); + connect(m_rotationCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, &VideoViewerWidget::onRotationChanged); + transformLayout->addWidget(m_rotationCombo); + + m_flipHorizontal = new QCheckBox("Flip H", this); + connect(m_flipHorizontal, &QCheckBox::checkStateChanged, this, &VideoViewerWidget::onFlipHorizontalChanged); + transformLayout->addWidget(m_flipHorizontal); + + m_flipVertical = new QCheckBox("Flip V", this); + connect(m_flipVertical, &QCheckBox::checkStateChanged, this, &VideoViewerWidget::onFlipVerticalChanged); + transformLayout->addWidget(m_flipVertical); + + transformLayout->addStretch(); + auto* controlGroup = new QGroupBox("Viewer Controls", this); auto* controlLayout = new QVBoxLayout(); @@ -88,9 +119,11 @@ void VideoViewerWidget::setupUI() m_sourceType->addItem("TCP H.264 Stream", "tcp"); m_sourceType->addItem("MJPEG HTTP Stream", "http"); m_sourceType->addItem("Test Pattern", "test"); + m_sourceType->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); + m_sourceType->setMinimumContentsLength(15); connect(m_sourceType, QOverload::of(&QComboBox::currentIndexChanged), this, &VideoViewerWidget::onSourceTypeChanged); - sourceLayout->addWidget(m_sourceType); + sourceLayout->addWidget(m_sourceType, 1); auto* formLayout = new QFormLayout(); m_hostEdit = new QLineEdit("127.0.0.1", this); @@ -117,9 +150,11 @@ void VideoViewerWidget::setupUI() controlLayout->addLayout(buttonLayout); controlLayout->addWidget(m_statusLabel); controlGroup->setLayout(controlLayout); + controlGroup->setMaximumWidth(500); mainLayout->addWidget(m_scrollArea, 1); mainLayout->addLayout(zoomLayout); + mainLayout->addLayout(transformLayout); mainLayout->addWidget(controlGroup); setLayout(mainLayout); @@ -352,6 +387,33 @@ void VideoViewerWidget::onZoomChanged(int value) } } +void VideoViewerWidget::onRotationChanged(int index) +{ + m_rotationAngle = m_rotationCombo->currentData().toInt(); + + if (!m_currentFrame.isNull()) { + displayFrame(m_currentFrame); + } +} + +void VideoViewerWidget::onFlipHorizontalChanged(Qt::CheckState state) +{ + m_flipHorizontalEnabled = (state == Qt::Checked); + + if (!m_currentFrame.isNull()) { + displayFrame(m_currentFrame); + } +} + +void VideoViewerWidget::onFlipVerticalChanged(Qt::CheckState state) +{ + m_flipVerticalEnabled = (state == Qt::Checked); + + if (!m_currentFrame.isNull()) { + displayFrame(m_currentFrame); + } +} + GstFlowReturn VideoViewerWidget::newSampleCallback(GstAppSink* appsink, gpointer user_data) { static int callbackCount = 0; @@ -453,13 +515,34 @@ void VideoViewerWidget::displayFrame(const QImage& frame) m_currentFrame = frame; - QPixmap pixmap = QPixmap::fromImage(frame); + QImage transformedFrame = frame; + + // Apply rotation + if (m_rotationAngle != 0) { + QTransform rotation; + rotation.rotate(m_rotationAngle); + transformedFrame = transformedFrame.transformed(rotation, Qt::SmoothTransformation); + } + + // Apply flipping + if (m_flipHorizontalEnabled || m_flipVerticalEnabled) { + Qt::Orientations orientations; + if (m_flipHorizontalEnabled) { + orientations |= Qt::Horizontal; + } + if (m_flipVerticalEnabled) { + orientations |= Qt::Vertical; + } + transformedFrame = transformedFrame.flipped(orientations); + } + + QPixmap pixmap = QPixmap::fromImage(transformedFrame); if (pixmap.isNull()) { qDebug() << "[VideoViewer] ERROR: Pixmap conversion failed!"; return; } - QSize targetSize = frame.size() * m_zoomFactor; + QSize targetSize = transformedFrame.size() * m_zoomFactor; QPixmap scaledPixmap = pixmap.scaled(targetSize, Qt::KeepAspectRatio, diff --git a/videoviewerwidget.h b/videoviewerwidget.h index 89f5437..3db275e 100644 --- a/videoviewerwidget.h +++ b/videoviewerwidget.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -28,6 +29,9 @@ private slots: void onStopViewer(); void onSourceTypeChanged(int index); void onZoomChanged(int value); + void onRotationChanged(int index); + void onFlipHorizontalChanged(Qt::CheckState state); + void onFlipVerticalChanged(Qt::CheckState state); void displayFrame(const QImage& frame); private: @@ -52,6 +56,9 @@ private: QLabel* m_statusLabel; QSlider* m_zoomSlider; QLabel* m_zoomLabel; + QComboBox* m_rotationCombo; + QCheckBox* m_flipHorizontal; + QCheckBox* m_flipVertical; // GStreamer elements (8-byte pointers) GstElement* m_pipeline; @@ -63,6 +70,11 @@ private: // Bus watch ID (4-byte unsigned int) guint m_busWatchId; + // Transformation settings (4 bytes + 2 bytes) + int m_rotationAngle; + bool m_flipHorizontalEnabled; + bool m_flipVerticalEnabled; + // Video state (large object at end) QImage m_currentFrame; };