fix:图像显示修复触摸屏下放大缩小问题

This commit is contained in:
YangSn 2026-09-19 09:54:22 +08:00
parent 7225f03ded
commit 76b63103af
3 changed files with 249 additions and 49 deletions

View File

@ -620,6 +620,11 @@ void PointCloudGLWidget::drawPrimitives(const QMatrix4x4& mvp,
glUniformMatrix4fv(m_mvpLocation, 1, GL_FALSE, mvp.constData()); glUniformMatrix4fv(m_mvpLocation, 1, GL_FALSE, mvp.constData());
glUniform1f(m_pointSizeLocation, pointSize); glUniform1f(m_pointSizeLocation, pointSize);
glUniform1f(m_alphaLocation, alpha); glUniform1f(m_alphaLocation, alpha);
if (!m_dynamicVbo.isCreated())
{
m_dynamicVbo.create();
}
m_dynamicVbo.setUsagePattern(QOpenGLBuffer::DynamicDraw);
if (depthTest) { if (depthTest) {
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
} else { } else {
@ -2889,6 +2894,7 @@ void PointCloudGLWidget::clearBasicShapeSegments()
void PointCloudGLWidget::addPosePoints(const QVector<PosePoint>& poses) void PointCloudGLWidget::addPosePoints(const QVector<PosePoint>& poses)
{ {
m_posePoints.append(poses); m_posePoints.append(poses);
computeBoundingBox();
update(); update();
} }
@ -2993,66 +2999,138 @@ void PointCloudGLWidget::drawLineSegmentList(const QVector<LineSegment>& segment
void PointCloudGLWidget::drawPosePoints() void PointCloudGLWidget::drawPosePoints()
{ {
if (m_posePoints.isEmpty()) { if (m_posePoints.isEmpty())
{
return; return;
} }
glLineWidth(2.0f); // 从视图矩阵取相机右/上方向,把轴线拉成面向屏幕的窄带,避免 GLES2 线宽被钳成 1 像素。
const QMatrix4x4 invView = m_view.inverted();
const QVector3D camRight = invView.column(0).toVector3D().normalized();
const QVector3D camUp = invView.column(1).toVector3D().normalized();
const float pixelWorld = m_distance *
std::tan(qDegreesToRadians(22.5f)) /
static_cast<float>(qMax(1, height() / 2));
for (const auto& pose : m_posePoints) { auto applyEulerRotation = [this](QMatrix4x4& poseLocal, const PosePoint& pose)
// 移动到姿态点位置,并按欧拉角顺序旋转(QMatrix4x4::rotate 与 glRotatef 同为右乘) {
QMatrix4x4 modelView; switch (m_eulerRotationOrder)
modelView.setToIdentity(); {
modelView.translate(pose.x, pose.y, pose.z); case EulerRotationOrder::ZYX:
switch (m_eulerRotationOrder) { poseLocal.rotate(pose.rx, 1, 0, 0);
case EulerRotationOrder::ZYX: // Yaw-Pitch-Roll(最常用) poseLocal.rotate(pose.ry, 0, 1, 0);
modelView.rotate(pose.rx, 1, 0, 0); poseLocal.rotate(pose.rz, 0, 0, 1);
modelView.rotate(pose.ry, 0, 1, 0);
modelView.rotate(pose.rz, 0, 0, 1);
break; break;
case EulerRotationOrder::XYZ: // Roll-Pitch-Yaw case EulerRotationOrder::XYZ:
modelView.rotate(pose.rz, 0, 0, 1); poseLocal.rotate(pose.rz, 0, 0, 1);
modelView.rotate(pose.ry, 0, 1, 0); poseLocal.rotate(pose.ry, 0, 1, 0);
modelView.rotate(pose.rx, 1, 0, 0); poseLocal.rotate(pose.rx, 1, 0, 0);
break; break;
case EulerRotationOrder::ZXY: // Yaw-Roll-Pitch case EulerRotationOrder::ZXY:
modelView.rotate(pose.ry, 0, 1, 0); poseLocal.rotate(pose.ry, 0, 1, 0);
modelView.rotate(pose.rx, 1, 0, 0); poseLocal.rotate(pose.rx, 1, 0, 0);
modelView.rotate(pose.rz, 0, 0, 1); poseLocal.rotate(pose.rz, 0, 0, 1);
break; break;
case EulerRotationOrder::YXZ: // Pitch-Roll-Yaw case EulerRotationOrder::YXZ:
modelView.rotate(pose.rz, 0, 0, 1); poseLocal.rotate(pose.rz, 0, 0, 1);
modelView.rotate(pose.rx, 1, 0, 0); poseLocal.rotate(pose.rx, 1, 0, 0);
modelView.rotate(pose.ry, 0, 1, 0); poseLocal.rotate(pose.ry, 0, 1, 0);
break; break;
case EulerRotationOrder::XZY: // Roll-Yaw-Pitch case EulerRotationOrder::XZY:
modelView.rotate(pose.ry, 0, 1, 0); poseLocal.rotate(pose.ry, 0, 1, 0);
modelView.rotate(pose.rz, 0, 0, 1); poseLocal.rotate(pose.rz, 0, 0, 1);
modelView.rotate(pose.rx, 1, 0, 0); poseLocal.rotate(pose.rx, 1, 0, 0);
break; break;
case EulerRotationOrder::YZX: // Pitch-Yaw-Roll case EulerRotationOrder::YZX:
modelView.rotate(pose.rx, 1, 0, 0); poseLocal.rotate(pose.rx, 1, 0, 0);
modelView.rotate(pose.rz, 0, 0, 1); poseLocal.rotate(pose.rz, 0, 0, 1);
modelView.rotate(pose.ry, 0, 1, 0); poseLocal.rotate(pose.ry, 0, 1, 0);
break; break;
} }
};
const QMatrix4x4 mvp = m_projection * modelView; auto appendWorld = [](QVector<float>& dst, const QVector3D& p)
{
dst.append(p.x());
dst.append(p.y());
dst.append(p.z());
};
// 绘制坐标系(右手坐标系:X红右,Y绿上,Z蓝前) auto appendAxisBand = [&](QVector<float>& positions,
const float positions[] = { QVector<float>& colors,
0.0f, 0.0f, 0.0f, pose.scale, 0.0f, 0.0f, // X轴 - 红色 const QVector3D& start,
0.0f, 0.0f, 0.0f, 0.0f, pose.scale, 0.0f, // Y轴 - 绿色 const QVector3D& end,
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, pose.scale // Z轴 - 蓝色 const QVector3D& color,
float halfWidth)
{
QVector3D side = QVector3D::crossProduct(end - start, camUp);
if (side.lengthSquared() < 1.0e-12f)
{
side = QVector3D::crossProduct(end - start, camRight);
}
if (!side.isNull())
{
side.normalize();
side *= halfWidth;
}
const QVector3D s0 = start - side;
const QVector3D s1 = start + side;
const QVector3D e0 = end - side;
const QVector3D e1 = end + side;
const QVector3D quad[6] = {s0, e0, e1, s0, e1, s1};
for (const QVector3D& p : quad)
{
appendWorld(positions, p);
appendWorld(colors, color);
}
};
for (const auto& pose : m_posePoints)
{
QMatrix4x4 poseLocal;
poseLocal.setToIdentity();
poseLocal.translate(pose.x, pose.y, pose.z);
applyEulerRotation(poseLocal, pose);
const float axisLength = qMax(1.0f, pose.scale);
const QVector3D origin = poseLocal.map(QVector3D(0.0f, 0.0f, 0.0f));
const QVector3D xEnd = poseLocal.map(QVector3D(axisLength, 0.0f, 0.0f));
const QVector3D yEnd = poseLocal.map(QVector3D(0.0f, axisLength, 0.0f));
const QVector3D zEnd = poseLocal.map(QVector3D(0.0f, 0.0f, axisLength));
const float halfWidth = qMax(axisLength * 0.02f, pixelWorld * 2.0f);
QVector<float> bandPositions;
QVector<float> bandColors;
bandPositions.reserve(54);
bandColors.reserve(54);
appendAxisBand(bandPositions, bandColors, origin, xEnd, QVector3D(1.0f, 0.0f, 0.0f), halfWidth);
appendAxisBand(bandPositions, bandColors, origin, yEnd, QVector3D(0.0f, 1.0f, 0.0f), halfWidth);
appendAxisBand(bandPositions, bandColors, origin, zEnd, QVector3D(0.0f, 0.0f, 1.0f), halfWidth);
// 关闭深度测试,避免姿态轴被周围点云挡住。
drawPrimitives(m_mvp,
bandPositions.constData(),
bandPositions.size() / 3,
GL_TRIANGLES,
bandColors.constData(),
QVector3D(1.0f, 1.0f, 1.0f),
1.0f,
1.0f,
false);
const float tipPositions[] = {
origin.x(), origin.y(), origin.z(),
xEnd.x(), xEnd.y(), xEnd.z(),
yEnd.x(), yEnd.y(), yEnd.z(),
zEnd.x(), zEnd.y(), zEnd.z()
}; };
const float colors[] = { const float tipColors[] = {
1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f
}; };
drawPrimitives(mvp, positions, 6, GL_LINES, colors, drawPrimitives(m_mvp, tipPositions, 4, GL_POINTS, tipColors,
QVector3D(1.0f, 1.0f, 1.0f), 1.0f, 1.0f, true); QVector3D(1.0f, 1.0f, 1.0f), 10.0f, 1.0f, false);
} }
glLineWidth(1.0f);
} }

View File

@ -6,9 +6,12 @@
#include <Qt> #include <Qt>
class QLabel; class QLabel;
class QEvent;
class QGestureEvent;
class QGraphicsPixmapItem; class QGraphicsPixmapItem;
class QGraphicsScene; class QGraphicsScene;
class QMouseEvent; class QMouseEvent;
class QPinchGesture;
class QWheelEvent; class QWheelEvent;
// 可缩放、可平移的图片显示控件。 // 可缩放、可平移的图片显示控件。
@ -48,6 +51,7 @@ signals:
void zoomRatioChanged(double ratio); void zoomRatioChanged(double ratio);
protected: protected:
bool viewportEvent(QEvent* event) override;
void resizeEvent(QResizeEvent* event) override; void resizeEvent(QResizeEvent* event) override;
void wheelEvent(QWheelEvent* event) override; void wheelEvent(QWheelEvent* event) override;
void mousePressEvent(QMouseEvent* event) override; void mousePressEvent(QMouseEvent* event) override;
@ -56,8 +60,11 @@ protected:
void mouseDoubleClickEvent(QMouseEvent* event) override; void mouseDoubleClickEvent(QMouseEvent* event) override;
private: private:
bool handleGestureEvent(QGestureEvent* event);
void handlePinchGesture(QPinchGesture* gesture);
void updateZoomLabel(); void updateZoomLabel();
void updateZoomRatio(double ratio, bool keepCenter); void updateZoomRatio(double ratio, bool keepCenter);
void updateZoomRatioAt(double ratio, const QPoint& viewportAnchor);
void repositionZoomLabel(); void repositionZoomLabel();
QGraphicsScene* m_scene; QGraphicsScene* m_scene;
@ -69,6 +76,8 @@ private:
double m_maximumZoomRatio; double m_maximumZoomRatio;
Qt::Alignment m_imageAlignment; Qt::Alignment m_imageAlignment;
bool m_panning; bool m_panning;
bool m_pinching;
bool m_needsInitialFit; bool m_needsInitialFit;
double m_pinchStartZoomRatio;
QPoint m_lastMousePosition; QPoint m_lastMousePosition;
}; };

View File

@ -1,19 +1,24 @@
#include "ImageView.h" #include "ImageView.h"
#include <QEvent>
#include <QGestureEvent>
#include <QGraphicsPixmapItem> #include <QGraphicsPixmapItem>
#include <QGraphicsScene> #include <QGraphicsScene>
#include <QLabel> #include <QLabel>
#include <QMouseEvent> #include <QMouseEvent>
#include <QPinchGesture>
#include <QResizeEvent> #include <QResizeEvent>
#include <QtWidgets/QScrollBar> #include <QtWidgets/QScrollBar>
#include <QStyle> #include <QStyle>
#include <QWheelEvent> #include <QWheelEvent>
#include <QtMath> #include <QtMath>
namespace { namespace
{
constexpr double kDefaultMinimumZoom = 0.02; constexpr double kDefaultMinimumZoom = 0.02;
constexpr double kDefaultMaximumZoom = 20.0; constexpr double kDefaultMaximumZoom = 20.0;
constexpr double kZoomStep = 1.25; constexpr double kZoomStep = 1.25;
const QColor kDefaultBackgroundColor(37, 38, 42);
} }
ImageView::ImageView(QWidget* parent) ImageView::ImageView(QWidget* parent)
@ -26,10 +31,12 @@ ImageView::ImageView(QWidget* parent)
m_maximumZoomRatio(kDefaultMaximumZoom), m_maximumZoomRatio(kDefaultMaximumZoom),
m_imageAlignment(Qt::AlignCenter), m_imageAlignment(Qt::AlignCenter),
m_panning(false), m_panning(false),
m_needsInitialFit(false) m_pinching(false),
m_needsInitialFit(false),
m_pinchStartZoomRatio(1.0)
{ {
setScene(m_scene); setScene(m_scene);
setBackgroundBrush(QColor(35, 35, 35)); setBackgroundBrush(kDefaultBackgroundColor);
setFrameShape(QFrame::NoFrame); setFrameShape(QFrame::NoFrame);
setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
@ -39,6 +46,10 @@ ImageView::ImageView(QWidget* parent)
setDragMode(QGraphicsView::NoDrag); setDragMode(QGraphicsView::NoDrag);
setCursor(Qt::OpenHandCursor); setCursor(Qt::OpenHandCursor);
// 手势由 QGraphicsView 的视口接收,支持触摸屏双指捏合缩放。
viewport()->setAttribute(Qt::WA_AcceptTouchEvents, true);
viewport()->grabGesture(Qt::PinchGesture);
m_zoomLabel->setObjectName(QStringLiteral("imageViewZoomLabel")); m_zoomLabel->setObjectName(QStringLiteral("imageViewZoomLabel"));
m_zoomLabel->setAlignment(Qt::AlignCenter); m_zoomLabel->setAlignment(Qt::AlignCenter);
m_zoomLabel->setMinimumWidth(58); m_zoomLabel->setMinimumWidth(58);
@ -197,6 +208,65 @@ Qt::Alignment ImageView::imageAlignment() const
return m_imageAlignment; return m_imageAlignment;
} }
bool ImageView::viewportEvent(QEvent* event)
{
if (event->type() == QEvent::Gesture)
{
return handleGestureEvent(static_cast<QGestureEvent*>(event));
}
return QGraphicsView::viewportEvent(event);
}
bool ImageView::handleGestureEvent(QGestureEvent* event)
{
QGesture* gesture = event->gesture(Qt::PinchGesture);
if (!gesture)
{
return false;
}
QPinchGesture* pinchGesture = static_cast<QPinchGesture*>(gesture);
handlePinchGesture(pinchGesture);
event->accept(pinchGesture);
return true;
}
void ImageView::handlePinchGesture(QPinchGesture* gesture)
{
if (!gesture)
{
return;
}
if (gesture->state() == Qt::GestureStarted)
{
// 双指手势开始后停止单指模拟产生的鼠标拖拽。
m_pinching = true;
m_panning = false;
m_pinchStartZoomRatio = m_zoomRatio;
setCursor(Qt::OpenHandCursor);
}
if (hasImage() &&
(gesture->state() == Qt::GestureStarted || gesture->state() == Qt::GestureUpdated))
{
const double scaleFactor = gesture->totalScaleFactor();
if (qIsFinite(scaleFactor) && scaleFactor > 0.0)
{
// 使用累计缩放比例,避免连续事件带来的比例漂移。
updateZoomRatioAt(m_pinchStartZoomRatio * scaleFactor,
gesture->centerPoint().toPoint());
}
}
if (gesture->state() == Qt::GestureFinished ||
gesture->state() == Qt::GestureCanceled)
{
m_pinching = false;
setCursor(Qt::OpenHandCursor);
}
}
void ImageView::resizeEvent(QResizeEvent* event) void ImageView::resizeEvent(QResizeEvent* event)
{ {
QGraphicsView::resizeEvent(event); QGraphicsView::resizeEvent(event);
@ -221,6 +291,11 @@ void ImageView::wheelEvent(QWheelEvent* event)
void ImageView::mousePressEvent(QMouseEvent* event) void ImageView::mousePressEvent(QMouseEvent* event)
{ {
if (m_pinching)
{
event->accept();
return;
}
if (event->button() == Qt::LeftButton && hasImage()) { if (event->button() == Qt::LeftButton && hasImage()) {
m_panning = true; m_panning = true;
m_lastMousePosition = event->pos(); m_lastMousePosition = event->pos();
@ -233,6 +308,11 @@ void ImageView::mousePressEvent(QMouseEvent* event)
void ImageView::mouseMoveEvent(QMouseEvent* event) void ImageView::mouseMoveEvent(QMouseEvent* event)
{ {
if (m_pinching)
{
event->accept();
return;
}
if (m_panning) { if (m_panning) {
const QPoint delta = event->pos() - m_lastMousePosition; const QPoint delta = event->pos() - m_lastMousePosition;
m_lastMousePosition = event->pos(); m_lastMousePosition = event->pos();
@ -246,6 +326,11 @@ void ImageView::mouseMoveEvent(QMouseEvent* event)
void ImageView::mouseReleaseEvent(QMouseEvent* event) void ImageView::mouseReleaseEvent(QMouseEvent* event)
{ {
if (m_pinching)
{
event->accept();
return;
}
if (event->button() == Qt::LeftButton && m_panning) { if (event->button() == Qt::LeftButton && m_panning) {
m_panning = false; m_panning = false;
setCursor(Qt::OpenHandCursor); setCursor(Qt::OpenHandCursor);
@ -300,6 +385,34 @@ void ImageView::updateZoomRatio(double ratio, bool keepCenter)
emit zoomRatioChanged(m_zoomRatio); emit zoomRatioChanged(m_zoomRatio);
} }
void ImageView::updateZoomRatioAt(double ratio, const QPoint& viewportAnchor)
{
if (!hasImage())
{
return;
}
ratio = qBound(m_minimumZoomRatio, ratio, m_maximumZoomRatio);
if (qFuzzyCompare(ratio, m_zoomRatio) && !transform().isIdentity())
{
return;
}
// 缩放前后保持双指中心对应同一个图像位置。
const QPointF sceneAnchor = mapToScene(viewportAnchor);
resetTransform();
scale(ratio, ratio);
m_zoomRatio = ratio;
const QPointF mappedAnchor = mapFromScene(sceneAnchor);
const QPointF viewportDelta = mappedAnchor - viewportAnchor;
horizontalScrollBar()->setValue(
horizontalScrollBar()->value() + qRound(viewportDelta.x()));
verticalScrollBar()->setValue(
verticalScrollBar()->value() + qRound(viewportDelta.y()));
updateZoomLabel();
emit zoomRatioChanged(m_zoomRatio);
}
void ImageView::repositionZoomLabel() void ImageView::repositionZoomLabel()
{ {
if (!m_zoomLabel) { if (!m_zoomLabel) {