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());
glUniform1f(m_pointSizeLocation, pointSize);
glUniform1f(m_alphaLocation, alpha);
if (!m_dynamicVbo.isCreated())
{
m_dynamicVbo.create();
}
m_dynamicVbo.setUsagePattern(QOpenGLBuffer::DynamicDraw);
if (depthTest) {
glEnable(GL_DEPTH_TEST);
} else {
@ -2889,6 +2894,7 @@ void PointCloudGLWidget::clearBasicShapeSegments()
void PointCloudGLWidget::addPosePoints(const QVector<PosePoint>& poses)
{
m_posePoints.append(poses);
computeBoundingBox();
update();
}
@ -2993,66 +2999,138 @@ void PointCloudGLWidget::drawLineSegmentList(const QVector<LineSegment>& segment
void PointCloudGLWidget::drawPosePoints()
{
if (m_posePoints.isEmpty()) {
if (m_posePoints.isEmpty())
{
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) {
// 移动到姿态点位置,并按欧拉角顺序旋转(QMatrix4x4::rotate 与 glRotatef 同为右乘)
QMatrix4x4 modelView;
modelView.setToIdentity();
modelView.translate(pose.x, pose.y, pose.z);
switch (m_eulerRotationOrder) {
case EulerRotationOrder::ZYX: // Yaw-Pitch-Roll(最常用)
modelView.rotate(pose.rx, 1, 0, 0);
modelView.rotate(pose.ry, 0, 1, 0);
modelView.rotate(pose.rz, 0, 0, 1);
auto applyEulerRotation = [this](QMatrix4x4& poseLocal, const PosePoint& pose)
{
switch (m_eulerRotationOrder)
{
case EulerRotationOrder::ZYX:
poseLocal.rotate(pose.rx, 1, 0, 0);
poseLocal.rotate(pose.ry, 0, 1, 0);
poseLocal.rotate(pose.rz, 0, 0, 1);
break;
case EulerRotationOrder::XYZ: // Roll-Pitch-Yaw
modelView.rotate(pose.rz, 0, 0, 1);
modelView.rotate(pose.ry, 0, 1, 0);
modelView.rotate(pose.rx, 1, 0, 0);
case EulerRotationOrder::XYZ:
poseLocal.rotate(pose.rz, 0, 0, 1);
poseLocal.rotate(pose.ry, 0, 1, 0);
poseLocal.rotate(pose.rx, 1, 0, 0);
break;
case EulerRotationOrder::ZXY: // Yaw-Roll-Pitch
modelView.rotate(pose.ry, 0, 1, 0);
modelView.rotate(pose.rx, 1, 0, 0);
modelView.rotate(pose.rz, 0, 0, 1);
case EulerRotationOrder::ZXY:
poseLocal.rotate(pose.ry, 0, 1, 0);
poseLocal.rotate(pose.rx, 1, 0, 0);
poseLocal.rotate(pose.rz, 0, 0, 1);
break;
case EulerRotationOrder::YXZ: // Pitch-Roll-Yaw
modelView.rotate(pose.rz, 0, 0, 1);
modelView.rotate(pose.rx, 1, 0, 0);
modelView.rotate(pose.ry, 0, 1, 0);
case EulerRotationOrder::YXZ:
poseLocal.rotate(pose.rz, 0, 0, 1);
poseLocal.rotate(pose.rx, 1, 0, 0);
poseLocal.rotate(pose.ry, 0, 1, 0);
break;
case EulerRotationOrder::XZY: // Roll-Yaw-Pitch
modelView.rotate(pose.ry, 0, 1, 0);
modelView.rotate(pose.rz, 0, 0, 1);
modelView.rotate(pose.rx, 1, 0, 0);
case EulerRotationOrder::XZY:
poseLocal.rotate(pose.ry, 0, 1, 0);
poseLocal.rotate(pose.rz, 0, 0, 1);
poseLocal.rotate(pose.rx, 1, 0, 0);
break;
case EulerRotationOrder::YZX: // Pitch-Yaw-Roll
modelView.rotate(pose.rx, 1, 0, 0);
modelView.rotate(pose.rz, 0, 0, 1);
modelView.rotate(pose.ry, 0, 1, 0);
case EulerRotationOrder::YZX:
poseLocal.rotate(pose.rx, 1, 0, 0);
poseLocal.rotate(pose.rz, 0, 0, 1);
poseLocal.rotate(pose.ry, 0, 1, 0);
break;
}
const QMatrix4x4 mvp = m_projection * modelView;
// 绘制坐标系(右手坐标系:X红右,Y绿上,Z蓝前)
const float positions[] = {
0.0f, 0.0f, 0.0f, pose.scale, 0.0f, 0.0f, // X轴 - 红色
0.0f, 0.0f, 0.0f, 0.0f, pose.scale, 0.0f, // Y轴 - 绿色
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, pose.scale // Z轴 - 蓝色
};
const float colors[] = {
1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
auto appendWorld = [](QVector<float>& dst, const QVector3D& p)
{
dst.append(p.x());
dst.append(p.y());
dst.append(p.z());
};
drawPrimitives(mvp, positions, 6, GL_LINES, colors,
QVector3D(1.0f, 1.0f, 1.0f), 1.0f, 1.0f, true);
auto appendAxisBand = [&](QVector<float>& positions,
QVector<float>& colors,
const QVector3D& start,
const QVector3D& end,
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);
}
};
glLineWidth(1.0f);
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 tipColors[] = {
1.0f, 1.0f, 1.0f,
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f
};
drawPrimitives(m_mvp, tipPositions, 4, GL_POINTS, tipColors,
QVector3D(1.0f, 1.0f, 1.0f), 10.0f, 1.0f, false);
}
}

View File

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

View File

@ -1,19 +1,24 @@
#include "ImageView.h"
#include <QEvent>
#include <QGestureEvent>
#include <QGraphicsPixmapItem>
#include <QGraphicsScene>
#include <QLabel>
#include <QMouseEvent>
#include <QPinchGesture>
#include <QResizeEvent>
#include <QtWidgets/QScrollBar>
#include <QStyle>
#include <QWheelEvent>
#include <QtMath>
namespace {
namespace
{
constexpr double kDefaultMinimumZoom = 0.02;
constexpr double kDefaultMaximumZoom = 20.0;
constexpr double kZoomStep = 1.25;
const QColor kDefaultBackgroundColor(37, 38, 42);
}
ImageView::ImageView(QWidget* parent)
@ -26,10 +31,12 @@ ImageView::ImageView(QWidget* parent)
m_maximumZoomRatio(kDefaultMaximumZoom),
m_imageAlignment(Qt::AlignCenter),
m_panning(false),
m_needsInitialFit(false)
m_pinching(false),
m_needsInitialFit(false),
m_pinchStartZoomRatio(1.0)
{
setScene(m_scene);
setBackgroundBrush(QColor(35, 35, 35));
setBackgroundBrush(kDefaultBackgroundColor);
setFrameShape(QFrame::NoFrame);
setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
@ -39,6 +46,10 @@ ImageView::ImageView(QWidget* parent)
setDragMode(QGraphicsView::NoDrag);
setCursor(Qt::OpenHandCursor);
// 手势由 QGraphicsView 的视口接收,支持触摸屏双指捏合缩放。
viewport()->setAttribute(Qt::WA_AcceptTouchEvents, true);
viewport()->grabGesture(Qt::PinchGesture);
m_zoomLabel->setObjectName(QStringLiteral("imageViewZoomLabel"));
m_zoomLabel->setAlignment(Qt::AlignCenter);
m_zoomLabel->setMinimumWidth(58);
@ -197,6 +208,65 @@ Qt::Alignment ImageView::imageAlignment() const
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)
{
QGraphicsView::resizeEvent(event);
@ -221,6 +291,11 @@ void ImageView::wheelEvent(QWheelEvent* event)
void ImageView::mousePressEvent(QMouseEvent* event)
{
if (m_pinching)
{
event->accept();
return;
}
if (event->button() == Qt::LeftButton && hasImage()) {
m_panning = true;
m_lastMousePosition = event->pos();
@ -233,6 +308,11 @@ void ImageView::mousePressEvent(QMouseEvent* event)
void ImageView::mouseMoveEvent(QMouseEvent* event)
{
if (m_pinching)
{
event->accept();
return;
}
if (m_panning) {
const QPoint delta = event->pos() - m_lastMousePosition;
m_lastMousePosition = event->pos();
@ -246,6 +326,11 @@ void ImageView::mouseMoveEvent(QMouseEvent* event)
void ImageView::mouseReleaseEvent(QMouseEvent* event)
{
if (m_pinching)
{
event->accept();
return;
}
if (event->button() == Qt::LeftButton && m_panning) {
m_panning = false;
setCursor(Qt::OpenHandCursor);
@ -300,6 +385,34 @@ void ImageView::updateZoomRatio(double ratio, bool keepCenter)
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()
{
if (!m_zoomLabel) {