weld_measure/weld_opengl_widget.cpp
2026-08-12 08:44:39 -03:00

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();
}