提要
需求:載入一張圖片並顯示,能夠放大縮小,能夠截取圖片的某個矩形並保存。
原以為蠻簡單的一個功能,事實上還是有點小復雜。
最簡單Qt圖片瀏覽器能夠參考Qt自帶的Demo:Image Viewer Example
看一下終於的實現效果:



圖片的載入顯示
這里須要實現一個QImageViewer的類。繼承自QWidget。
圖片用QPixmap來載入和顯示,還有三個成員各自是圖片的縮放因子,圖片是否已經載入,viewer是否已經初始化,是否處於裁剪狀態。
private: QPixmap m_pixmap; float scalling; bool isLoaded; bool isIntialised; bool isCropping;
scalling值是用於記錄圖片的縮放比例。
顯示圖片僅僅要又一次定義paintEvent,在里面繪制m_pixmap就能夠了。
void QImageViewer::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
if (m_pixmap.isNull())
{
return;
}
QPainter painter(this);
if (isLoaded)
{
painter.setRenderHint(QPainter::SmoothPixmapTransform);
QSize pixSize = m_pixmap.size();
//For canvas's size not change when window's size change.
if (!isInitialised)
{
QSize initialSize = event->rect().size();
scaling = 1.0 * initialSize.width() / pixSize.width();
isInitialised = true;
}
pixSize.scale(scaling * pixSize, Qt::KeepAspectRatio);
this->setMinimumSize(pixSize);
QPoint topleft;
topleft.setX((this->width() - pixSize.width()) / 2);
topleft.setY((this->height() - pixSize.height()) / 2);
painter.drawPixmap(topleft, m_pixmap.scaled(pixSize, Qt::KeepAspectRatio, Qt::SmoothTransformation));
}
}
截圖
思路非常easy。首先選擇進入裁剪模式(能夠在menu中加入一個action或者用快捷鍵),然后在圖片上拖出一個矩形。最后按回車。截取完畢,Ctrl + s 保存。
主要用到的是 QPixmap::copy(QRect rect) 方法。
首先要實現一個CropRect類,用於記錄截取的矩形。
#ifndef CROPRECT_H
#define CROPRECT_H
#include <QPoint>
#include <QPainter>
class CropRect
{
public:
CropRect(){}
~CropRect(){}
void setStart(QPoint s)
{
start = s;
}
void setEnd(QPoint e)
{
end = e;
}
QPoint startPoint() const
{
return start;
}
QPoint endPoint() const
{
return end;
}
void reset()
{
QPoint P(0,0);
start = P;
end = P;
}
QSize& size() const
{
return QSize(width(), height());
}
int height() const
{
return qAbs(startPoint().y() - endPoint().y());
}
int width() const
{
return qAbs(startPoint().x() - endPoint().x());
}
private:
QPoint start;
QPoint end;
};
#endif // CROPRECT_H
注意這里的start和end都是相對於當前要截取的圖片的位置。也就是圖片的左上角的坐標是CropRect的原點。
接下來再QImageviewer中實現兩個輔助的方法。
由於圖片並非恰好全然充滿窗口,所以在設定裁剪框的時候,鼠標假設沒有點在圖片上,就應該不裁剪。所以首先應該推斷屏幕中的某個點是否在圖片上。
bool QImageViewer::isContainPoint(QPoint &p)
{
QSize s = m_pixmap.size();
s.scale(scaling * s, Qt::KeepAspectRatio);
//If pixmap bigger than current window.
if ((s.height() > this->rect().height()) && (s.width() > this->rect().width()))
{
return true;
}
QPoint topleft;
topleft.setX((this->width() - s.width()) / 2);
topleft.setY((this->height() - s.height()) / 2);
QRect rect(topleft, s);
return rect.contains(p);
}
第二個方法就是將鼠標的位置映射到圖片上的位置,由於截圖主要是對圖片進行操作。
圖片的大小和窗口大小有四種情況,第一種是圖片高度和寬度都大於窗口。

邏輯就是將紅色和綠色部分相加,得到點對於當前圖片(已縮放)的位置。最后除以scalling就能夠了。
另一種是圖片全然在窗口里面

這樣的情況將紅色減去綠色部分,得到點對於當前圖片(已縮放)的位置。最后除以scalling就能夠了。還有兩種簡單的情況就不展開了,詳細代碼例如以下:
QPoint QImageViewer::mapToPixmap(QPoint &screenPoint)
{
QSize pixmapSize = m_pixmap.size();
pixmapSize.scale(scaling * pixmapSize, Qt::KeepAspectRatio);
//Get the position of screenPoint to the pixmap in show.
QPoint tmpPos;
if (pixmapSize.width() > this->width() && pixmapSize.height() > this->height())
{
tmpPos.setX(pixmapSize.width() - (this->width() - screenPoint.x()));
tmpPos.setY(pixmapSize.height() - (this->height() - screenPoint.y()));
}
else if (pixmapSize.width() < this->width() && pixmapSize.height() > this->height())
{
tmpPos.setX(screenPoint.x() - (this->width() - pixmapSize.width()) / 2);
tmpPos.setY(pixmapSize.height() - (this->height() - screenPoint.y()));
}
else if (pixmapSize.width() > this->width() && pixmapSize.height() < this->height())
{
tmpPos.setX(pixmapSize.width() - (this->width() - screenPoint.x()));
tmpPos.setY(screenPoint.y() - (this->height() - pixmapSize.height()) / 2);
}
else{
QPoint topleft;
topleft.setX((this->width() - pixmapSize.width()) / 2);
topleft.setY((this->height() - pixmapSize.height()) / 2);
tmpPos.setX(screenPoint.x() - topleft.x());
tmpPos.setY(screenPoint.y() - topleft.y());
}
//return the position to the real pixmap.*/
return QPoint(tmpPos.x() / scaling, tmpPos.y() / scaling);
}
這里採取了一個投機取巧的辦法。就是利用了QPoint.setX() 和 QPoint.setY()方法假設傳進去的是負值,那么就等於傳進去0,所以少了一些小於0的推斷。
接下來就是相應的鼠標事件,用於確定裁剪框的大小
void QImageViewer::mousePressEvent(QMouseEvent *event)
{
if ((event->buttons() == Qt::LeftButton) && isContainPoint(event->pos()) && isCropping)
{
cropRect.setStart(mapToPixmap(event->pos()));
cropRect.setEnd(mapToPixmap(event->pos()));
isStartingCrop = true;
}
}
void QImageViewer::mouseMoveEvent(QMouseEvent *event)
{
if ((event->buttons() == Qt::LeftButton) && isStartingCrop)
{
if (isContainPoint(event->pos()))
{
cropRect.setEnd(mapToPixmap(event->pos()));
update();
}
}
}
void QImageViewer::mouseReleaseEvent(QMouseEvent *e)
{
QRect rect(cropRect.startPoint(), cropRect.endPoint());
isStartingCrop = false;
}
裁剪框繪制的相關代碼。這里也依據startpoint 和endpoint的相對位置,也有幾種情況須要注意一下。
更炫酷的動態螞蟻線能夠參考:Qt中繪制螞蟻線
if (isCropping)
{
qDebug() << cropRect.width() << cropRect.height();
//painter.setPen(Qt::darkGreen);
QPen pen;
pen.setBrush(Qt::red);
pen.setStyle(Qt::DashLine);
pen.setWidth(1);
painter.setPen(pen);
//start point in the left to the end point.
if (cropRect.startPoint().x() < cropRect.endPoint().x())
{
if (cropRect.startPoint().y() < cropRect.endPoint().y())
{
//start point in the top to the end point.
painter.drawRect(topleft.x() + cropRect.startPoint().x() * scaling, topleft.y() + cropRect.startPoint().y() * scaling, cropRect.width() * scaling, cropRect.height() * scaling);
}
else{
//start point in the bottom to the end point.
painter.drawRect(topleft.x() + cropRect.startPoint().x() * scaling, topleft.y() + cropRect.endPoint().y() * scaling, cropRect.width() * scaling, cropRect.height() * scaling);
}
}
else
{
if (cropRect.startPoint().y() > cropRect.endPoint().y())
{
painter.drawRect(topleft.x() + cropRect.endPoint().x() * scaling, topleft.y() + cropRect.endPoint().y() * scaling, cropRect.width() * scaling, cropRect.height() * scaling);
}
else{
painter.drawRect(topleft.x() + cropRect.endPoint().x() * scaling, topleft.y() + cropRect.startPoint().y() * scaling, cropRect.width() * scaling, cropRect.height() * scaling);
}
}
}
最后就是裁剪了
void QImageViewer::cropFinished()
{
QRect crop(cropRect.startPoint(), QSize(cropRect.width(), cropRect.height()));
QPixmap cropped = m_pixmap.copy(crop);
m_pixmap = cropped;
cropRect.reset();
isCropping = false;
this->update();
}
打完收工。
