1. 程式人生 > >Qt中截圖功能的實現

Qt中截圖功能的實現

提要

需求:載入一張圖片並顯示,可以放大縮小,可以擷取圖片的某個矩形並儲存。

原以為蠻簡單的一個功能,其實還是有點小複雜。

看一下最終的實現效果:



圖片的載入顯示

這裡需要實現一個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));
	}
}

截圖

思路很簡單,首先選擇進入裁剪模式(可以在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();
}

打完收工。