add image rotation and flipping
This commit is contained in:
@@ -31,6 +31,7 @@ void CameraControlWidget::setupUI()
|
|||||||
|
|
||||||
scrollWidget->setLayout(scrollLayout);
|
scrollWidget->setLayout(scrollLayout);
|
||||||
scrollArea->setWidget(scrollWidget);
|
scrollArea->setWidget(scrollWidget);
|
||||||
|
scrollWidget->setMaximumWidth(500);
|
||||||
|
|
||||||
mainLayout->addWidget(scrollArea);
|
mainLayout->addWidget(scrollArea);
|
||||||
|
|
||||||
@@ -48,6 +49,8 @@ QGroupBox* CameraControlWidget::createFormatGroup()
|
|||||||
|
|
||||||
m_formatCombo = new QComboBox(this);
|
m_formatCombo = new QComboBox(this);
|
||||||
m_formatCombo->addItem("1280x720@30fps UYVY (Supported)", "1280,720,30,UYVY");
|
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_getFormatsBtn = new QPushButton("Get Available Formats", this);
|
||||||
m_setFormatBtn = new QPushButton("Set Format", this);
|
m_setFormatBtn = new QPushButton("Set Format", this);
|
||||||
@@ -221,7 +224,7 @@ void CameraControlWidget::onGetFormats()
|
|||||||
QString format = fmt["format"].toString();
|
QString format = fmt["format"].toString();
|
||||||
|
|
||||||
QString displayText = QString("%1x%2@%3fps %4").arg(width, height, fps).arg(format);
|
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);
|
m_formatCombo->addItem(displayText, data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ void GStreamerPipelineWidget::setupUI()
|
|||||||
auto* formatLabel = new QLabel("Video Format:", this);
|
auto* formatLabel = new QLabel("Video Format:", this);
|
||||||
m_formatCombo = new QComboBox(this);
|
m_formatCombo = new QComboBox(this);
|
||||||
m_formatCombo->addItem("1280x720@30fps UYVY (Default/Supported)", "1280,720,30,UYVY");
|
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(formatLabel);
|
||||||
groupLayout->addWidget(m_formatCombo);
|
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("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("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->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<int>::of(&QComboBox::currentIndexChanged),
|
connect(m_pipelinePresets, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||||
this, &GStreamerPipelineWidget::onPipelinePresetChanged);
|
this, &GStreamerPipelineWidget::onPipelinePresetChanged);
|
||||||
@@ -91,6 +95,8 @@ void GStreamerPipelineWidget::setupUI()
|
|||||||
m_statusLabel->setStyleSheet("QLabel { background-color: #f0f0f0; padding: 5px; border-radius: 3px; }");
|
m_statusLabel->setStyleSheet("QLabel { background-color: #f0f0f0; padding: 5px; border-radius: 3px; }");
|
||||||
groupLayout->addWidget(m_statusLabel);
|
groupLayout->addWidget(m_statusLabel);
|
||||||
|
|
||||||
|
groupBox->setMaximumWidth(500);
|
||||||
|
|
||||||
mainLayout->addWidget(groupBox);
|
mainLayout->addWidget(groupBox);
|
||||||
mainLayout->addStretch();
|
mainLayout->addStretch();
|
||||||
|
|
||||||
@@ -305,7 +311,7 @@ void GStreamerPipelineWidget::onFormatsReceived(const QJsonObject& response)
|
|||||||
QString format = fmt["format"].toString();
|
QString format = fmt["format"].toString();
|
||||||
|
|
||||||
QString displayText = QString("%1x%2@%3fps %4").arg(width, height, fps).arg(format);
|
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);
|
m_formatCombo->addItem(displayText, data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <QGroupBox>
|
#include <QGroupBox>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
|
#include <QTransform>
|
||||||
#include <gst/video/video.h>
|
#include <gst/video/video.h>
|
||||||
|
|
||||||
VideoViewerWidget::VideoViewerWidget(QWidget *parent)
|
VideoViewerWidget::VideoViewerWidget(QWidget *parent)
|
||||||
@@ -19,10 +20,16 @@ VideoViewerWidget::VideoViewerWidget(QWidget *parent)
|
|||||||
m_statusLabel(nullptr),
|
m_statusLabel(nullptr),
|
||||||
m_zoomSlider(nullptr),
|
m_zoomSlider(nullptr),
|
||||||
m_zoomLabel(nullptr),
|
m_zoomLabel(nullptr),
|
||||||
|
m_rotationCombo(nullptr),
|
||||||
|
m_flipHorizontal(nullptr),
|
||||||
|
m_flipVertical(nullptr),
|
||||||
m_pipeline(nullptr),
|
m_pipeline(nullptr),
|
||||||
m_appSink(nullptr),
|
m_appSink(nullptr),
|
||||||
m_zoomFactor(1.0),
|
m_zoomFactor(1.0),
|
||||||
m_busWatchId(0)
|
m_busWatchId(0),
|
||||||
|
m_rotationAngle(0),
|
||||||
|
m_flipHorizontalEnabled(false),
|
||||||
|
m_flipVerticalEnabled(false)
|
||||||
{
|
{
|
||||||
initGStreamer();
|
initGStreamer();
|
||||||
setupUI();
|
setupUI();
|
||||||
@@ -77,6 +84,30 @@ void VideoViewerWidget::setupUI()
|
|||||||
zoomLayout->addWidget(m_zoomSlider);
|
zoomLayout->addWidget(m_zoomSlider);
|
||||||
zoomLayout->addWidget(m_zoomLabel);
|
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<int>::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* controlGroup = new QGroupBox("Viewer Controls", this);
|
||||||
auto* controlLayout = new QVBoxLayout();
|
auto* controlLayout = new QVBoxLayout();
|
||||||
|
|
||||||
@@ -88,9 +119,11 @@ void VideoViewerWidget::setupUI()
|
|||||||
m_sourceType->addItem("TCP H.264 Stream", "tcp");
|
m_sourceType->addItem("TCP H.264 Stream", "tcp");
|
||||||
m_sourceType->addItem("MJPEG HTTP Stream", "http");
|
m_sourceType->addItem("MJPEG HTTP Stream", "http");
|
||||||
m_sourceType->addItem("Test Pattern", "test");
|
m_sourceType->addItem("Test Pattern", "test");
|
||||||
|
m_sourceType->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
|
||||||
|
m_sourceType->setMinimumContentsLength(15);
|
||||||
connect(m_sourceType, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
connect(m_sourceType, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||||
this, &VideoViewerWidget::onSourceTypeChanged);
|
this, &VideoViewerWidget::onSourceTypeChanged);
|
||||||
sourceLayout->addWidget(m_sourceType);
|
sourceLayout->addWidget(m_sourceType, 1);
|
||||||
|
|
||||||
auto* formLayout = new QFormLayout();
|
auto* formLayout = new QFormLayout();
|
||||||
m_hostEdit = new QLineEdit("127.0.0.1", this);
|
m_hostEdit = new QLineEdit("127.0.0.1", this);
|
||||||
@@ -117,9 +150,11 @@ void VideoViewerWidget::setupUI()
|
|||||||
controlLayout->addLayout(buttonLayout);
|
controlLayout->addLayout(buttonLayout);
|
||||||
controlLayout->addWidget(m_statusLabel);
|
controlLayout->addWidget(m_statusLabel);
|
||||||
controlGroup->setLayout(controlLayout);
|
controlGroup->setLayout(controlLayout);
|
||||||
|
controlGroup->setMaximumWidth(500);
|
||||||
|
|
||||||
mainLayout->addWidget(m_scrollArea, 1);
|
mainLayout->addWidget(m_scrollArea, 1);
|
||||||
mainLayout->addLayout(zoomLayout);
|
mainLayout->addLayout(zoomLayout);
|
||||||
|
mainLayout->addLayout(transformLayout);
|
||||||
mainLayout->addWidget(controlGroup);
|
mainLayout->addWidget(controlGroup);
|
||||||
|
|
||||||
setLayout(mainLayout);
|
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)
|
GstFlowReturn VideoViewerWidget::newSampleCallback(GstAppSink* appsink, gpointer user_data)
|
||||||
{
|
{
|
||||||
static int callbackCount = 0;
|
static int callbackCount = 0;
|
||||||
@@ -453,13 +515,34 @@ void VideoViewerWidget::displayFrame(const QImage& frame)
|
|||||||
|
|
||||||
m_currentFrame = 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()) {
|
if (pixmap.isNull()) {
|
||||||
qDebug() << "[VideoViewer] ERROR: Pixmap conversion failed!";
|
qDebug() << "[VideoViewer] ERROR: Pixmap conversion failed!";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize targetSize = frame.size() * m_zoomFactor;
|
QSize targetSize = transformedFrame.size() * m_zoomFactor;
|
||||||
|
|
||||||
QPixmap scaledPixmap = pixmap.scaled(targetSize,
|
QPixmap scaledPixmap = pixmap.scaled(targetSize,
|
||||||
Qt::KeepAspectRatio,
|
Qt::KeepAspectRatio,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QSlider>
|
#include <QSlider>
|
||||||
#include <QScrollArea>
|
#include <QScrollArea>
|
||||||
|
#include <QCheckBox>
|
||||||
#include <gst/gst.h>
|
#include <gst/gst.h>
|
||||||
#include <gst/app/gstappsink.h>
|
#include <gst/app/gstappsink.h>
|
||||||
|
|
||||||
@@ -28,6 +29,9 @@ private slots:
|
|||||||
void onStopViewer();
|
void onStopViewer();
|
||||||
void onSourceTypeChanged(int index);
|
void onSourceTypeChanged(int index);
|
||||||
void onZoomChanged(int value);
|
void onZoomChanged(int value);
|
||||||
|
void onRotationChanged(int index);
|
||||||
|
void onFlipHorizontalChanged(Qt::CheckState state);
|
||||||
|
void onFlipVerticalChanged(Qt::CheckState state);
|
||||||
void displayFrame(const QImage& frame);
|
void displayFrame(const QImage& frame);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -52,6 +56,9 @@ private:
|
|||||||
QLabel* m_statusLabel;
|
QLabel* m_statusLabel;
|
||||||
QSlider* m_zoomSlider;
|
QSlider* m_zoomSlider;
|
||||||
QLabel* m_zoomLabel;
|
QLabel* m_zoomLabel;
|
||||||
|
QComboBox* m_rotationCombo;
|
||||||
|
QCheckBox* m_flipHorizontal;
|
||||||
|
QCheckBox* m_flipVertical;
|
||||||
|
|
||||||
// GStreamer elements (8-byte pointers)
|
// GStreamer elements (8-byte pointers)
|
||||||
GstElement* m_pipeline;
|
GstElement* m_pipeline;
|
||||||
@@ -63,6 +70,11 @@ private:
|
|||||||
// Bus watch ID (4-byte unsigned int)
|
// Bus watch ID (4-byte unsigned int)
|
||||||
guint m_busWatchId;
|
guint m_busWatchId;
|
||||||
|
|
||||||
|
// Transformation settings (4 bytes + 2 bytes)
|
||||||
|
int m_rotationAngle;
|
||||||
|
bool m_flipHorizontalEnabled;
|
||||||
|
bool m_flipVerticalEnabled;
|
||||||
|
|
||||||
// Video state (large object at end)
|
// Video state (large object at end)
|
||||||
QImage m_currentFrame;
|
QImage m_currentFrame;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user