#include "weld_opengl_widget.h" #include #include #include #include #include WeldOpenGLWidget::WeldOpenGLWidget(QWidget *parent) : QOpenGLWidget(parent) { setMouseTracking(true); } WeldOpenGLWidget::~WeldOpenGLWidget() { makeCurrent(); if (m_texture) { delete m_texture; m_texture = nullptr; } doneCurrent(); } void WeldOpenGLWidget::initializeGL() { initializeOpenGLFunctions(); glClearColor(0.15f, 0.15f, 0.18f, 1.0f); } void WeldOpenGLWidget::resizeGL(int w, int h) { glViewport(0, 0, w, h); } bool WeldOpenGLWidget::loadImage(const QString &filePath) { QImage img(filePath); if (img.isNull()) return false; setImage(img); return true; } void WeldOpenGLWidget::setImage(const QImage &image) { m_image = image.convertToFormat(QImage::Format_RGBA8888); m_textureNeedsUpdate = true; // Reset zoom and center image m_zoomLevel = 1.0; m_panOffset = QPointF(0, 0); // Initialize single measurement/calibration line int w = m_image.width(); int h = m_image.height(); m_head = QPointF(w * 0.3, h * 0.5); m_tail = QPointF(w * 0.7, h * 0.5); m_hasLine = true; emit imageLoaded(w, h); emit calibrationUpdated(m_mmPerPixel); emit measurementUpdated(measuredPx(), measuredMm()); update(); } void WeldOpenGLWidget::calibrateWithCurrentLine(double knownMm) { double px = measuredPx(); if (px > 0.0 && knownMm > 0.0) { m_mmPerPixel = knownMm / px; m_isCalibrated = true; } emit calibrationUpdated(m_mmPerPixel); emit measurementUpdated(measuredPx(), measuredMm()); update(); } double WeldOpenGLWidget::measuredPx() const { if (!m_hasLine) return 0.0; return QLineF(m_head, m_tail).length(); } double WeldOpenGLWidget::measuredMm() const { return measuredPx() * m_mmPerPixel; } void WeldOpenGLWidget::resetLine() { if (!m_image.isNull()) { int w = m_image.width(); int h = m_image.height(); m_head = QPointF(w * 0.3, h * 0.5); m_tail = QPointF(w * 0.7, h * 0.5); m_hasLine = true; emit measurementUpdated(measuredPx(), measuredMm()); update(); } } void WeldOpenGLWidget::setLinePoints(const QPointF &head, const QPointF &tail) { m_head = head; m_tail = tail; m_hasLine = true; emit measurementUpdated(measuredPx(), measuredMm()); update(); } void WeldOpenGLWidget::paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (m_image.isNull()) return; makeCurrent(); if (m_textureNeedsUpdate) { if (m_texture) delete m_texture; m_texture = new QOpenGLTexture(m_image); m_texture->setMinificationFilter(QOpenGLTexture::Linear); m_texture->setMagnificationFilter(QOpenGLTexture::Linear); m_textureNeedsUpdate = false; } // Render 2D image via QPainter over OpenGL viewport QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); painter.setRenderHint(QPainter::TextAntialiasing, true); painter.setRenderHint(QPainter::SmoothPixmapTransform, true); // Calculate image destination rectangle with zoom and pan double widgetAspect = (double)width() / height(); double imageAspect = (double)m_image.width() / m_image.height(); double drawW, drawH; if (widgetAspect > imageAspect) { drawH = height() * m_zoomLevel; drawW = drawH * imageAspect; } else { drawW = width() * m_zoomLevel; drawH = drawW / imageAspect; } double drawX = (width() - drawW) / 2.0 + m_panOffset.x(); double drawY = (height() - drawH) / 2.0 + m_panOffset.y(); QRectF targetRect(drawX, drawY, drawW, drawH); painter.drawImage(targetRect, m_image); // Draw the single measurement/calibration line if (m_hasLine) { QColor color(255, 45, 45); // Smooth Vivid Red drawLineOverlay(painter, m_head, m_tail, color); } } void WeldOpenGLWidget::drawLineOverlay(QPainter &painter, const QPointF &imgHead, const QPointF &imgTail, const QColor &color) { QPointF wHead = imageToWidgetCoords(imgHead); QPointF wTail = imageToWidgetCoords(imgTail); painter.setRenderHint(QPainter::Antialiasing, true); // Pure single red line (1.0px thin stroke with antialiasing) QPen linePen(color, 1.0, Qt::SolidLine, Qt::RoundCap); painter.setPen(linePen); painter.drawLine(wHead, wTail); // Compact drag handles (short perpendicular ticks) QLineF line(wHead, wTail); double dx = line.dx(); double dy = line.dy(); double len = line.length(); if (len > 0.0) { double px = -dy / len; double py = dx / len; double tickHalfLen = 6.0; // short length perpendicular to measurement line QPointF headP1 = wHead + QPointF(px * tickHalfLen, py * tickHalfLen); QPointF headP2 = wHead - QPointF(px * tickHalfLen, py * tickHalfLen); painter.drawLine(headP1, headP2); QPointF tailP1 = wTail + QPointF(px * tickHalfLen, py * tickHalfLen); QPointF tailP2 = wTail - QPointF(px * tickHalfLen, py * tickHalfLen); painter.drawLine(tailP1, tailP2); } // End labels painter.setFont(QFont("Arial", 8, QFont::Bold)); painter.setPen(Qt::white); painter.drawText(wHead + QPointF(10, -6), "A"); painter.drawText(wTail + QPointF(10, -6), "B"); } QPointF WeldOpenGLWidget::widgetToImageCoords(const QPointF &widgetPos) const { if (m_image.isNull()) return QPointF(); double widgetAspect = (double)width() / height(); double imageAspect = (double)m_image.width() / m_image.height(); double drawW, drawH; if (widgetAspect > imageAspect) { drawH = height() * m_zoomLevel; drawW = drawH * imageAspect; } else { drawW = width() * m_zoomLevel; drawH = drawW / imageAspect; } double drawX = (width() - drawW) / 2.0 + m_panOffset.x(); double drawY = (height() - drawH) / 2.0 + m_panOffset.y(); double normX = (widgetPos.x() - drawX) / drawW; double normY = (widgetPos.y() - drawY) / drawH; return QPointF(normX * m_image.width(), normY * m_image.height()); } QPointF WeldOpenGLWidget::imageToWidgetCoords(const QPointF &imgPos) const { if (m_image.isNull()) return QPointF(); double widgetAspect = (double)width() / height(); double imageAspect = (double)m_image.width() / m_image.height(); double drawW, drawH; if (widgetAspect > imageAspect) { drawH = height() * m_zoomLevel; drawW = drawH * imageAspect; } else { drawW = width() * m_zoomLevel; drawH = drawW / imageAspect; } double drawX = (width() - drawW) / 2.0 + m_panOffset.x(); double drawY = (height() - drawH) / 2.0 + m_panOffset.y(); double normX = imgPos.x() / m_image.width(); double normY = imgPos.y() / m_image.height(); return QPointF(drawX + normX * drawW, drawY + normY * drawH); } WeldOpenGLWidget::DragHandle WeldOpenGLWidget::hitTestLine(const QPointF &imgPos, const QPointF &head, const QPointF &tail) const { QPointF wPos = imageToWidgetCoords(imgPos); QPointF wHead = imageToWidgetCoords(head); QPointF wTail = imageToWidgetCoords(tail); const double handleRadius = 15.0; // Hit test tolerance in pixels if (QLineF(wPos, wHead).length() <= handleRadius) return DragHandle::Head; if (QLineF(wPos, wTail).length() <= handleRadius) return DragHandle::Tail; // Check if clicked near line body QLineF line(wHead, wTail); double lineLen = line.length(); if (lineLen > 0) { double u = ((wPos.x() - wHead.x()) * (wTail.x() - wHead.x()) + (wPos.y() - wHead.y()) * (wTail.y() - wHead.y())) / (lineLen * lineLen); if (u >= 0.0 && u <= 1.0) { QPointF proj = wHead + u * (wTail - wHead); if (QLineF(wPos, proj).length() <= handleRadius) { return DragHandle::Body; } } } return DragHandle::None; } void WeldOpenGLWidget::mousePressEvent(QMouseEvent *event) { if (m_image.isNull()) return; if (event->button() == Qt::LeftButton) { QPointF imgPos = widgetToImageCoords(event->position()); m_activeHandle = hitTestLine(imgPos, m_head, m_tail); if (m_activeHandle != DragHandle::None) { m_dragStartImgPos = imgPos; m_dragStartHead = m_head; m_dragStartTail = m_tail; update(); } else { // Clicked background -> Pan image on left click drag m_isPanning = true; m_lastMousePos = event->pos(); setCursor(Qt::ClosedHandCursor); } return; } if (event->button() == Qt::RightButton) { m_isPanning = true; m_lastMousePos = event->pos(); setCursor(Qt::ClosedHandCursor); return; } } void WeldOpenGLWidget::mouseMoveEvent(QMouseEvent *event) { if (m_isPanning) { QPoint delta = event->pos() - m_lastMousePos; m_panOffset += QPointF(delta.x(), delta.y()); m_lastMousePos = event->pos(); update(); return; } QPointF imgPos = widgetToImageCoords(event->position()); if (m_activeHandle != DragHandle::None) { if (m_activeHandle == DragHandle::Head) { m_head = imgPos; } else if (m_activeHandle == DragHandle::Tail) { m_tail = imgPos; } else if (m_activeHandle == DragHandle::Body) { QPointF delta = imgPos - m_dragStartImgPos; m_head = m_dragStartHead + delta; m_tail = m_dragStartTail + delta; } emit measurementUpdated(measuredPx(), measuredMm()); update(); return; } // Update cursor feedback based on hover DragHandle hover = hitTestLine(imgPos, m_head, m_tail); if (hover == DragHandle::Head || hover == DragHandle::Tail) { setCursor(Qt::CrossCursor); } else if (hover == DragHandle::Body) { setCursor(Qt::SizeAllCursor); } else { setCursor(Qt::OpenHandCursor); } } void WeldOpenGLWidget::mouseReleaseEvent(QMouseEvent *event) { if (m_isPanning && (event->button() == Qt::LeftButton || event->button() == Qt::RightButton)) { m_isPanning = false; QPointF imgPos = widgetToImageCoords(event->position()); DragHandle hover = hitTestLine(imgPos, m_head, m_tail); if (hover == DragHandle::Head || hover == DragHandle::Tail) setCursor(Qt::CrossCursor); else if (hover == DragHandle::Body) setCursor(Qt::SizeAllCursor); else setCursor(Qt::OpenHandCursor); } if (event->button() == Qt::LeftButton && m_activeHandle != DragHandle::None) { m_activeHandle = DragHandle::None; QPointF imgPos = widgetToImageCoords(event->position()); DragHandle hover = hitTestLine(imgPos, m_head, m_tail); if (hover == DragHandle::Head || hover == DragHandle::Tail) setCursor(Qt::CrossCursor); else if (hover == DragHandle::Body) setCursor(Qt::SizeAllCursor); else setCursor(Qt::OpenHandCursor); } } void WeldOpenGLWidget::wheelEvent(QWheelEvent *event) { double zoomFactor = 1.15; if (event->angleDelta().y() < 0) { zoomFactor = 1.0 / zoomFactor; } double newZoom = m_zoomLevel * zoomFactor; if (newZoom < 0.2) newZoom = 0.2; if (newZoom > 25.0) newZoom = 25.0; QPointF mousePos = event->position(); QPointF imgPosBefore = widgetToImageCoords(mousePos); m_zoomLevel = newZoom; QPointF widgetPosAfter = imageToWidgetCoords(imgPosBefore); m_panOffset += (mousePos - widgetPosAfter); update(); }