
实现了基于Qt的图像查看器组件,主要包含两个核心类:ImageViewerWidget和ToolPanZoom。ImageViewerWidget作为图像显示控件,支持灰度图像和彩色图像显示,提供拖放文件、图像缩放和平移等功能。它通过转换矩阵数据为QImage实现可视化,并使用jet色彩映射增强显示效果。ToolPanZoom类实现了图像平移和缩放功能,通过鼠标事件处理实现交互式操作,并确保图像始终在可见区域内。关键技术点包括:1) 矩阵数据到QImage的转换算法;2) 基于变换矩阵的图像缩放和平移实现;3) 鼠标事件处理与坐标转换;4) 图像边界检查和限制逻辑。该组件可作为专业的图像处理工具的基础模块。
ImageViewerWidget.cpp
cpp
#include "ImageViewerWidget.h"
#include <QPainter>
#include <QWheelEvent>
#include<QDebug>
#include <QMouseEvent>
#include<QHboxLayout>
#include <QtMath>
#include <QDragEnterEvent>
#include <QMimeData>
#include"Tools/ToolPanZoom.h"
#include<Tools/ToolManager.h>
ImageViewerWidget::ImageViewerWidget(QWidget *parent)
: QWidget(parent),
m_scale(1.0)
{
setMouseTracking(true);
//m_imageWidget2->setFixedSize(m_imageWidget->width(), m_imageWidget->height());
setAcceptDrops(true);
installEventFilter(this);
//m_pToolManager = new ToolManager(this);
jetcolorinit(0, 0);
m_imageWidget = new QWidget(this);
m_imageWidget->installEventFilter(this);
}
void ImageViewerWidget::setToolManager(ToolManager* pToolManager)
{
m_pToolManager = pToolManager;
m_pToolManager->AddTool(new ToolPanZoom(), this);
m_pToolManager->SetCurrentTool(EToolType::PanZoom);
}
ImageViewerWidget::~ImageViewerWidget()
{
if (image_data)
{
delete[] image_data;
image_data = nullptr;
}
delete m_imageWidget;
}
void ImageViewerWidget::setImage(const QImage& pixmap)
{
m_pixmap = pixmap;
resetZoom();
}
void ImageViewerWidget::setGray8Image(const QImage& pixmap)
{
format = ImageDataFormat::IMAGE_8UC1;
M_scale = 1.0;
//数据
image_Min = std::numeric_limits<float>::infinity();
image_Max = -std::numeric_limits<float>::infinity();
M_width = pixmap.width();//根据行进行设置
M_height = pixmap.height();//根据列进行设置
imageRatio = (qreal)M_width / M_height;
m_pixmap = pixmap;
resetZoom();
update();
}
void ImageViewerWidget::jetcolorinit(float max, float min)
{
int s;
float component;
if (max == 0 && min == 0)
component = 1;
else
component = abs(max - min);
//QColor(255, 101, 101)
for (s = 0; s < max * 256; s++)
{
jet[s][0] = 255;
jet[s][1] = 101;
jet[s][2] = 101;
}
qDebug() << "max" << max << "min" << min << "component" << component << "max*255" << max * 255;
for (s = 0; s < 32; s++) {
jet[(int)(s * component + max * 256)][0] = 128 + 4 * s;
jet[(int)s][1] = 0;
jet[(int)s][2] = 0;
}
jet[(int)(32 * component + max * 256)][0] = 255;
jet[(int)(32 * component + max * 256)][1] = 0;
jet[(int)(32 * component + max * 256)][2] = 0;
for (s = 0; s < 63; s++) {
jet[(int)((33 + s) * component + max * 256)][0] = 255;
jet[(int)((33 + s) * component + max * 256)][1] = 4 + 4 * s;
jet[(int)((33 + s) * component + max * 256)][2] = 0;
}
jet[(int)(96 * component + max * 256)][0] = 254;
jet[(int)(96 * component + max * 256)][1] = 255;
jet[(int)(96 * component + max * 256)][2] = 2;
for (s = 0; s < 62; s++) {
jet[(int)((97 + s) * component + max * 256)][0] = 250 - 4 * s;
jet[(int)((97 + s) * component + max * 256)][1] = 255;
jet[(int)((97 + s) * component + max * 256)][2] = 6 + 4 * s;
}
jet[(int)(159 * component + max * 256)][0] = 1;
jet[(int)(159 * component + max * 256)][1] = 255;
jet[(int)(159 * component + max * 256)][2] = 254;
for (s = 0; s < 64; s++) {
jet[(int)((160 + s) * component + max * 256)][0] = 0;
jet[(int)((160 + s) * component + max * 256)][1] = 252 - (s * 4);
jet[(int)((160 + s) * component + max * 256)][2] = 255;
}
for (s = 0; s < 32; s++) {
jet[(int)((224 + s) * component + max * 256)][0] = 0;
jet[(int)((224 + s) * component + max * 256)][1] = 0;
jet[(int)((224 + s) * component + max * 256)][2] = 252 - 4 * s;
}
for (int s = (255 * component + max * 256); s < 256; s++)
{
jet[s][0] = 0;
jet[s][1] = 0;
jet[s][2] = 128;
}
}
void ImageViewerWidget::convertMatToQImage(double** mat, int m_width, int m_height, float min, float max)
{
qDebug() << "m_width" << m_width << "m_height" << m_height << "min" << min << max;
m_pixmap = QImage(m_width, m_height, QImage::Format_RGB32);
double vlie = (double)256 / (abs(max - min));
m_pixmap.fill(255);
// Set the color table (used to translate colour indexes to qRgb values)
// Copy input MatM_range
for (int i = 0; i < m_height; i++)
{
for (int j = 0; j < m_width; j++)
{
if (std::isnan(mat[i][j]))
{
m_pixmap.setPixelColor(j, i, QColor(255, 255, 255));
}
else
{
int num = abs((mat[i][j] - min) * vlie);
if (num >= 255)
num = 255;
m_pixmap.setPixelColor(j, i, QColor(jet[255 - num][0],
jet[255 - num][1], jet[255 - num][2]));
}
}
}
// Set the color table (used to translate colour indexes to qRgb values)
}
void ImageViewerWidget::setimagedata(double** mat1, int width, int height, float scale, QVector<QPoint>NANVEC)
{
if (mat1 == nullptr)//一定要先判断是否为空,不为空再判断矩阵中是否有值
return;
QVector<double> local_vec;//
for (int i = 0; i < NANVEC.size(); i++)
{
local_vec.push_back(mat1[NANVEC[i].x()][NANVEC[i].y()]);
mat1[NANVEC[i].x()][NANVEC[i].y()] = NAN;
}
M_scale = scale;
image_data = mat1;
NAN_Vec = NANVEC;
//数据
image_Min = std::numeric_limits<float>::infinity();
image_Max = -std::numeric_limits<float>::infinity();
M_width = width;//根据行进行设置
M_height = height;//根据列进行设置
imageRatio = (qreal)M_width / M_height;
resetZoom();
//if (ratio_image > ratio_widget) {
// XY_unit_scale = (float)m_imageWidget->width() / M_width;
//}
//else {
// XY_unit_scale = (float)m_imageWidget->height() / M_height;
//}
////
int i, j;
float z;
for (i = 1; i <= M_height; i++)
{
int index = 0;
for (j = 1; j <= M_width; j++)
{
z = image_data[i - 1][j - 1] * Z_unit_scale;
if (!std::isnan(z))
{
if (z > image_Max)
image_Max = z;
if (z < image_Min)
image_Min = z;
}
}
}
convertMatToQImage(image_data, M_width, M_height, image_Min / Z_unit_scale, image_Max / Z_unit_scale);
for (int i = 0; i < NANVEC.size(); i++)
{
mat1[NANVEC[i].x()][NANVEC[i].y()] = local_vec.at(i);
}
//emit Send_Jet_Range(image_Min, image_Max, graph3D->axisY()->labelFormat());
update();
}
QImage ImageViewerWidget::image() const
{
return m_pixmap;
}
QRectF ImageViewerWidget::getImageRect() const
{
auto pt1 = imageToWindow(QPointF(0, 0));
auto pt2 = imageToWindow(QPointF(M_width-1, M_height-1));
QRectF result;
result.setX(pt1.x());
result.setY(pt1.y());
result.setWidth(pt2.x() - pt1.x() + 1);
result.setHeight(pt2.y() - pt1.y() + 1);
return result;
}
QRectF ImageViewerWidget::getImageWidgetRect() const
{
return m_imageWidget->rect();
}
int ImageViewerWidget::getImageWidth() const
{
return M_width;
}
int ImageViewerWidget::getImageHeight() const
{
return M_height;
}
bool ImageViewerWidget::isimageValid() const
{
if (M_width == 0 || M_height == 0)
return false;
return true;
}
void ImageViewerWidget::resetZoom()
{
if (isimageValid())
{
m_transform.reset();
m_transform.scale(m_scale, m_scale);
m_transform_init = m_transform;
}
}
double ImageViewerWidget::zoomFactor() const
{
return m_scale * m_zoomFactor;
}
void ImageViewerWidget::onPaintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter(m_imageWidget);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
painter.fillRect(rect(), QColor(240, 240, 240));
if (!m_pixmap.isNull())
{
if (format == ImageDataFormat::IMAGE_8UC1)
{
if (m_bHighlightSaturation)
{
QImage tmp = m_pixmap.convertToFormat(QImage::Format_ARGB32);
for (int y = 0; y < M_height; ++y)
{
const uchar* line = m_pixmap.constScanLine(y); // 每行首地址[^3^]
for (int x = 0; x < M_width; ++x)
{
if (line[x] >= m_uSaturationThreshold)
{
tmp.setPixelColor(QPoint(x, y), Qt::red);
}
}
}
painter.setWorldTransform(m_transform);
QRectF source(0.0, 0.0, M_width, M_height);
painter.drawImage(0, 0, tmp, Qt::ColorOnly);
}
else
{
painter.setWorldTransform(m_transform);
QRectF source(0.0, 0.0, M_width, M_height);
painter.drawImage(0, 0, m_pixmap, Qt::ColorOnly);
}
}
else if (format == ImageDataFormat::IMAGE_64FC1)
{
painter.setWorldTransform(m_transform);
QRectF source(0.0, 0.0, M_width, M_height);
painter.drawImage(0, 0, m_pixmap, Qt::ColorOnly);
}
}
else {
QImage imag = QImage(100, 100, QImage::Format_RGB32);
imag.fill(QColor(255, 255, 255));
QPainter mypainter(this);
mypainter.setRenderHint(QPainter::Antialiasing);
QPen pen;
pen.setColor(QColor(0, 0, 120));
pen.setWidth(2);
mypainter.setPen(pen);
QRectF target(0, 0, this->width(), this->height());
QRectF source(0.0, 0.0, 100, 100);
mypainter.drawImage(target, imag, source, Qt::ColorOnly);
return QWidget::paintEvent(event);
}
//QTransform tmpTrans;
//tmpTrans.reset();
//painter.setWorldTransform(tmpTrans);
//painter.setPen(QPen(Qt::red, 2, Qt::DashLine)); // 红色虚线,宽度2px
//painter.drawRect(rect().adjusted(1, 1, -1, -1)); // 向内缩进1px避免溢出
return QWidget::paintEvent(event);
}
void ImageViewerWidget::mousePressEvent(QMouseEvent* event)
{
if (!m_pixmap.isNull())
{
if (event->button() == Qt::RightButton)
{
//// 右键点击获取像素坐标
//QPoint pixelPos = windowToImage(event->pos());
//if (m_pixmap.rect().contains(pixelPos)) {
// emit pixelSelected(pixelPos);
//}
}
}
}
void ImageViewerWidget::mouseMoveEvent(QMouseEvent* event)
{
}
void ImageViewerWidget::mouseReleaseEvent(QMouseEvent *event)
{
}
void ImageViewerWidget::wheelEvent(QWheelEvent *event)
{
}
QPoint ImageViewerWidget::windowToImage(const QPoint& windowPos) const
{
QTransform invTransform = m_transform.inverted();
return invTransform.map(windowPos);
}
QPointF ImageViewerWidget::windowToImage(const QPointF &windowPos) const
{
QTransform invTransform = m_transform.inverted();
return invTransform.map(windowPos);
}
QPointF ImageViewerWidget::imageToWindow(const QPointF& imagePos) const
{
return m_transform.map(imagePos);
}
QPoint ImageViewerWidget::imageToWindow(const QPoint& imagePos) const
{
return m_transform.map(imagePos);
}
void ImageViewerWidget::updateShow()
{
update();
}
void ImageViewerWidget::setImageCursor(const QCursor& cursor)
{
setCursor(cursor);
}
void ImageViewerWidget::resizeEvent(QResizeEvent* event)
{
Q_UNUSED(event);
if (!m_pixmap.isNull()) {
resetZoom();
}
}
bool ImageViewerWidget::eventFilter(QObject* watched, QEvent* event)
{
auto toolType = EToolType::DefaultNull;
if (m_pToolManager && m_pToolManager->CurrentTool())
{
toolType = m_pToolManager->CurrentTool()->GetType();
}
if (!m_pixmap.isNull())
{
if (watched == m_imageWidget)
{
if (m_pToolManager)
{
switch (event->type())
{
case QEvent::MouseButtonPress:
m_pToolManager->MouseDown(static_cast<QMouseEvent*>(event));
break;
case QEvent::MouseMove:
m_pToolManager->MouseMove(static_cast<QMouseEvent*>(event));
break;
case QEvent::MouseButtonRelease:
m_pToolManager->MouseUp(static_cast<QMouseEvent*>(event));
break;
case QEvent::Wheel:
if (toolType != EToolType::PanZoom && m_pToolManager)
m_pToolManager->SetCurrentTool(EToolType::PanZoom);
m_pToolManager->MouseWheel(static_cast<QWheelEvent*>(event));
if (toolType != EToolType::PanZoom && m_pToolManager)
m_pToolManager->SetCurrentTool(toolType);
break;
}
}
this->onPaintEvent(static_cast<QPaintEvent*>(event));
}
switch (event->type())
{
case QEvent::DragEnter: // 拖入时
{
isDropingFile = true;
QDragEnterEvent* dragEvent = static_cast<QDragEnterEvent*>(event);
if (dragEvent->mimeData()->hasUrls()) // 检查是否有文件
{
dragEvent->acceptProposedAction(); // 接受拖放
return true; // 事件已处理
}
break;
}
case QEvent::Drop: // 放下时
{
QDropEvent* dropEvent = static_cast<QDropEvent*>(event);
const QMimeData* mimeData = dropEvent->mimeData();
if (mimeData->hasUrls()) // 检查是否有文件
{
QList<QUrl> urlList = mimeData->urls();
for (const QUrl& url : urlList)
{
QString filePath = url.toLocalFile(); // 获取文件路径
qDebug() << "Dropped file:" << filePath;
//emit fileDropped(filePath); // 发射信号
QPixmap pixmap;
try {
pixmap.load(filePath);
QImage image = pixmap.toImage().convertToFormat(QImage::Format_Grayscale8);
setGray8Image(image);
//convertToImageData(image);
//setimagedata(image_data, image.width(), image.height(), 1.0, QVector<QPoint>());
emit updateImageData(image_data, image.width(), image.height(), 1.0);
resetImageWidget();
}
catch (const std::exception& e) {
qCritical() << "Exception caught:" << e.what();
}
}
dropEvent->acceptProposedAction(); // 接受拖放
isDropingFile = false;
return true; // 事件已处理
}
break;
}
default:
break;
}
this->onPaintEvent(static_cast<QPaintEvent*>(event));
}
return QWidget::eventFilter(watched, event);
}
bool ImageViewerWidget::isInImageWidget(QPoint pt)
{
return m_imageWidget->rect().contains(pt);
}
void ImageViewerWidget::convertToImageData(QImage& image)
{
const int img_width = image.width();
const int img_height = image.height();
const int totalPixels = img_width * img_height;
if (image_data)
{
delete[] image_data;
image_data = nullptr;
}
image_data = new double* [img_height];
// 遍历像素并转换
for (int y = 0; y < img_height; ++y) {
image_data[y] = new double[img_width];
const uchar* scanLine = image.constScanLine(y); // 获取一行像素数据
for (int x = 0; x < img_width; ++x) {
double pixelValue = static_cast<double>(scanLine[x]);
image_data[y][x] = pixelValue;
}
}
}
void ImageViewerWidget::Set_Range_Max(float max, float Min)
{
if (image_data == nullptr)
return;
jetcolorinit(max, Min);
convertMatToQImage(image_data, M_width, M_height, image_Min, image_Max);
//
max = abs(max - Min);
//qDebug()<<"Max"<<max<<"Min"<<Min;
QLinearGradient gr;
gr.setColorAt(0.0, QColor(0, 0, 255));
gr.setColorAt(1 - Min, QColor(0, 0, 255));
gr.setColorAt(1 - Min + max / 4, QColor(0, 255, 255));
gr.setColorAt(1 - Min + (max / 4) * 2, QColor(0, 255, 0));
gr.setColorAt(1 - Min + (max / 4) * 3, QColor(255, 255, 0));
gr.setColorAt(1 - Min + max, QColor(255, 0, 0));
gr.setColorAt(1 - Min + max + 0.01, QColor(255, 101, 101, 255));
gr.setColorAt(1.0, QColor(255, 101, 101, 255));
emit UpdateSurfaceColorMap(gr);
}
void ImageViewerWidget::resetImageWidget()
{
if (isimageValid())
{
canvasRatio = (qreal)width() / height();
if (imageRatio > canvasRatio) {
// 以宽度为基准,图像宽占满canvas
qreal height = std::round(width() / imageRatio);
targetRect = QRectF(0, (this->height() - height) / 2, width(), height);
m_scale = (double)width() / getImageWidth();
}
else {
// 以高度为基准,图像高占满canvas
qreal width = std::round(height() * imageRatio);
targetRect = QRectF((this->width() - width) / 2, 0, width, height());
m_scale = (double)height() / getImageHeight();
}
m_imageWidget->setGeometry(targetRect.x(),targetRect.y(),targetRect.width(), targetRect.height());
resetZoom();
update();
}
}
ToolPanZoom.cpp
cpp
#include "ToolPanZoom.h"
#include <QMouseEvent>
#include <QWheelEvent>
#include <QMessageBox>
#include "ToolManager.h"
#include <QtMath>
#include<QDebug>
#include "ImageViewerWidget.h"
ToolPanZoom::ToolPanZoom(ToolManager* pCmdMgr/* = nullptr*/)
: ITool(pCmdMgr),
m_isDragging(false)
{
type = EToolType::DrawMask;
m_zoomTimer.start();
}
bool ToolPanZoom::IsEnable()
{
if (nullptr == m_pImageViewerWidget)
{
ImageViewerWidget* pWidget = qobject_cast<ImageViewerWidget*>(m_pHookWidget);
if (pWidget)
{
m_pImageViewerWidget = pWidget;
}
else
return false;
}
return true;
}
void ToolPanZoom::MouseDown(QMouseEvent* event)
{
if (IsEnable() && m_pImageViewerWidget->isInImageWidget(event->pos()))
{
if (event->button() == Qt::LeftButton) {
m_lastDragPos = event->pos();
m_isDragging = true;
m_pImageViewerWidget->setImageCursor(Qt::ClosedHandCursor);
}
}
}
void ToolPanZoom::MouseMove(QMouseEvent* event)
{
if (IsEnable() && m_pImageViewerWidget->isInImageWidget(event->pos()))
{
if (m_pImageViewerWidget->isViewLocked())
{
return;
}
if (m_isDragging)
{
qDebug() << "mouse move in";
// 计算移动距离
QPoint delta = event->pos() - m_lastDragPos;
m_lastDragPos = event->pos();
// 获取当前变换后的图像边界
QRectF imageRect = m_pImageViewerWidget->getImageRect();
qDebug() << "max" << imageRect.x() << " " << imageRect.y();
QRectF widgetRect = m_pImageViewerWidget->getImageWidgetRect();
// 计算平移后的位置
qreal dx = delta.x() / m_pImageViewerWidget->zoomFactor();
qreal dy = delta.y() / m_pImageViewerWidget->zoomFactor();
// 检查平移后是否会显示图像内部空白
QRectF translatedRect = imageRect.translated(dx, dy);
// 限制平移范围
if (translatedRect.left() > widgetRect.left()) {
dx = widgetRect.left() - imageRect.left();
dx /= m_pImageViewerWidget->zoomFactor();
}
if (translatedRect.right() < widgetRect.right()) {
dx = widgetRect.right() - imageRect.right();
dx /= m_pImageViewerWidget->zoomFactor();
}
if (translatedRect.top() > widgetRect.top()) {
dy = widgetRect.top() - imageRect.top();
dy /= m_pImageViewerWidget->zoomFactor();
}
if (translatedRect.bottom() < widgetRect.bottom()) {
dy = widgetRect.bottom() - imageRect.bottom();
dy /= m_pImageViewerWidget->zoomFactor();
}
// 应用限制后的平移
m_pImageViewerWidget->m_transform.translate(dx, dy);
m_pImageViewerWidget->updateShow();
qDebug() << "mouse move out";
}
}
}
void ToolPanZoom::MouseUp(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton && m_isDragging)
{
m_isDragging = false;
m_pImageViewerWidget->setImageCursor(Qt::ArrowCursor);
}
}
void ToolPanZoom::MouseWheel(QWheelEvent* event)
{
if (IsEnable() && m_pImageViewerWidget->isInImageWidget(event->pos()))
{
if (m_zoomTimer.elapsed() > 30) {
m_zoomTimer.restart();
// 获取鼠标位置作为缩放中心
QPointF mousePos = event->position();
// 计算缩放因子
double angle = event->angleDelta().y();
double factor = qPow(1.0015, angle);
scaleImage(factor, mousePos);
}
}
}
void ToolPanZoom::KeyDown(QKeyEvent* event)
{
/*switch (event->key())
{
case Qt::Key_Space:
break;
default:
break;
}*/
}
void ToolPanZoom::scaleImage(double factor, const QPointF& center)
{
// 限制缩放范围
double newZoomFactor = m_pImageViewerWidget->m_zoomFactor * factor;
if (newZoomFactor < 1.0)
newZoomFactor = 1.0;
if (newZoomFactor > 100) {
return;
}
m_pImageViewerWidget->m_zoomFactor = newZoomFactor;
double finalZoom = m_pImageViewerWidget->zoomFactor();
// 获取当前图像和窗口的矩形
QRectF widgetRect = m_pImageViewerWidget->getImageWidgetRect();
if (!center.isNull()) {
// 计算当前鼠标位置的图片坐标
QPointF imagePos = m_pImageViewerWidget->windowToImage(center.toPoint());
// 重置变换矩阵并应用新缩放
m_pImageViewerWidget->m_transform.reset();
m_pImageViewerWidget->m_transform.scale(m_pImageViewerWidget->zoomFactor(), m_pImageViewerWidget->zoomFactor());
// 计算缩放后鼠标应该对应的窗口坐标
QPointF targetWindowPos = center;
// 计算当前鼠标实际会对应的窗口坐标
QPointF actualWindowPos = m_pImageViewerWidget->m_transform.map(imagePos);
// 计算需要调整的平移量
QPointF delta = targetWindowPos - actualWindowPos;
if(newZoomFactor > 1.0)
m_pImageViewerWidget->m_transform.translate(delta.x() / finalZoom, delta.y() / finalZoom);
// 检查缩放后图像边界是否在widget内部
//QRectF transformedRect = m_pImageViewerWidget->m_transform.mapRect(imageRect);
QRectF transformedRect = m_pImageViewerWidget->getImageRect();
qreal dx = 0, dy = 0;
if (transformedRect.left() > widgetRect.left()) {
dx = widgetRect.left() - transformedRect.left();
}
if (transformedRect.right() < widgetRect.right()) {
dx = widgetRect.right() - transformedRect.right();
}
if (transformedRect.top() > widgetRect.top()) {
dy = widgetRect.top() - transformedRect.top();
}
if (transformedRect.bottom() < widgetRect.bottom()) {
dy = widgetRect.bottom() - transformedRect.bottom();
}
m_pImageViewerWidget->m_transform.translate(dx / finalZoom, dy / finalZoom);
}
m_pImageViewerWidget->updateShow();
}