387 lines
12 KiB
C++
387 lines
12 KiB
C++
#include "weld_opengl_widget.h"
|
|
#include <QPainter>
|
|
#include <QMouseEvent>
|
|
#include <QWheelEvent>
|
|
#include <QtMath>
|
|
#include <QDebug>
|
|
|
|
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();
|
|
}
|