需求:載入一張圖片並顯示,可以放大縮小,可以截取圖片的某個矩形並保存。html
原覺得蠻簡單的一個功能,事實上仍是有點小複雜。canvas
最簡單Qt圖片瀏覽器可以參考Qt自帶的Demo:Image Viewer Example瀏覽器
看一下終於的實現效果:this
這裏需要實現一個QImageViewer的類。繼承自QWidget。spa
圖片用QPixmap來載入和顯示,還有三個成員各自是圖片的縮放因子,圖片是否已經載入,viewer是否已經初始化,是否處於裁剪狀態。.net
private: QPixmap m_pixmap; float scalling; bool isLoaded; bool isIntialised; bool isCropping;
scalling值是用於記錄圖片的縮放比例。code
顯示圖片僅僅要又一次定義paintEvent,在裏面繪製m_pixmap就可以了。orm
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 保存。htm
主要用到的是 QPixmap::copy(QRect rect) 方法。blog
首先要實現一個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(); }打完收工。