1. Overview
Screenshots are a very common feature. However, the screenshot function of Windows is not easy to use. So many times we open QQ to make a screenshot. QQ screenshot function is still very powerful. Today, I will manually make a simple screenshot with Qt4. This screenshot is not comparable to the screenshot of QQ. It only realizes the basic functions. There is no complex operation for everyone to learn and reference.
2, Requirement description
The final product is an executable exe file, and the running platform is the windows operating system (after my test, it can run perfectly under Windows 7 and 10).
After opening the software, the screen darkens and enters "free mode", in which the mouse can move freely. Free mode can do the following:
- A picture is displayed on the free mode, indicating that the current mode is in free mode. Click the picture with the left mouse button, and the picture will disappear.
- Click the left mouse button to start capturing pictures, enter the "screenshot mode", and the pictures above will disappear automatically. In this mode, pressing and dragging the mouse will produce a rubber frame. Represents the area to capture. A QLabel will be generated in the upper left corner of the rubber frame to indicate the size (pixels) of the rubber frame.
- After releasing the mouse, the program will ask the user whether to save the picture. If the user selects "yes", a dialog box for saving the file will pop up, and the picture can only be saved in png format. The program will grab the picture of the rubber frame area, save and exit. If the user selects "no", the rubber frame and QLabel disappear.
- Double click the left mouse button in free mode to automatically capture the full screen.
- Right click in free mode and exit the program directly without screenshot.
3. Software screenshot
Free mode:
Screenshot mode:
Ask the user:
3, development environment
The software is developed by Qt 4.8.7, and the operating system is Windows10 64 bit. QtDesigner and QtCreator are not used.
The compilation environment is mingw32 4.8.2
4, analysis
Note: only the codes related to the screenshot function are analyzed below, and many details (such as picture tips and file saving) will not be explained.
To realize this program, the idea is actually very simple. We just need to get the current picture of the whole desktop and set this picture as the background picture of the main window. Then let the main window fill the whole desktop.
It's not difficult to get the whole desktop. Qt is directly provided and implemented. Let's look at the following member functions of QPixmap:
QPixmap grabWindow ( WId window, int x = 0, int y = 0, int width = -1, int height = -1 )
Care about the first parameter, which represents the Window ID to be intercepted. Here, we want to intercept the entire desktop. Just use QApplication:: desktop() - > winid(). The following parameters do not need to be concerned. By default, it intercepts the whole window.
We only need to set the background of the main window to this picture and the size to the size of the picture. You also need to hide the status bar of the main window. The implementation is also very simple. You can use setWindowFlags() to set QT:: frameleswindowhint.
After getting the QPixmap of the whole desktop, we need two copies. One is used to darken and display on the main window, and the other is the original for the real screenshot later.
A vexing question is how we darken the picture. I've found many ways. Here's a simple one: multiply all the three primary colors of the picture by a multiple. Note that QPixmap does not have a method to obtain three primary colors, so I have to convert QPixmap to QImage. Here I use an intermediate file temp png. If the efficiency is slightly low and there is a better method, you are welcome to comment.
whole_window.save("temp.png"); bg = QImage("temp.png"); QFile *temp = new QFile("temp.png"); temp->remove();
whole_window represents QPixmap captured by grabWindow. bg is a QImage class object. They are all members of the main window class.
Next, dim QImage using the following methods:
int red,green,blue; for(int i = x; i < width; i++){ for(int j = y; j < height; j++){ //All three primary colors are multiplied by a multiple to darken the screen red = qRed(bg.pixel(i, j)) * bright ; green = qGreen(bg.pixel(i, j)) * bright; blue = qBlue(bg.pixel(i, j)) * bright; bg.setPixel( i, j, qRgb(red, green, blue)); } }
Then we rewrite the paintEvent event event to set bg as the background image of the main window:
void screenShot::paintEvent(QPaintEvent *event){ QPainter painter(this); //Set bg as the background picture of the whole window painter.drawImage(0, 0, bg); }
This realizes the illusion of opening the program and darkening the whole desktop.
Next, the key to the whole program comes: when the user clicks the left mouse button, we need to start capturing pictures. This requires us to override the mouse event of the main window:
void mousePressEvent(QMouseEvent *e); void mouseMoveEvent(QMouseEvent *e); void mouseReleaseEvent(QMouseEvent *e); void mouseDoubleClickEvent(QMouseEvent *event);
The four events correspond to mouse press, move, release and double click.
We focus on mouse clicks.
When the mouse is pressed, we need to create a rubber frame to display the area of the snapshot. This uses a class called QRubberBand. Qt's help document describes this class as follows:
The QRubberBand class provides a rectangle or line that can indicate a selection or a boundary.
You can create a QRubberBand whenever you need to render a rubber band around a given area (or to represent a single line), then call setGeometry(), move() or resize() to position and size it. A common pattern is to do this in conjunction with mouse events.
The document says that this class can represent an area. We can change the size by resize and change the position of the box by move.
Then, set it as a member of the main window, and instantiate the rubber frame when the mouse clicks. When the mouse moves, change the size of the rubber frame. (the specific size needs to be calculated)
In order to realize the screenshot, we need to record the position where the mouse is pressed and released (calculate the length and width of the screenshot area). Save two QPoint objects start and end in the members of the main window.
This requires us to obtain the position of the mouse. It is very simple and can be completed through the pos() of QMouseEvent. Note that it returns a QPoint class object.
When the left mouse button is pressed, record the initial position of the mouse:
start = e->pos();
When the mouse moves, we need to change the size of the rubber frame in real time, as follows:
//Record where the mouse moves end = e->pos(); //Calculate the size of the box int width = abs(end.x() - start.x()); int height = abs(end.y() - start.y()); //Calculate the location of the box int x = start.x() < end.x() ? start.x() : end.x(); int y = start.y() < end.y() ? start.y() : end.y();
After the mouse is released, we need to record the click and release position of the mouse and calculate the size of the screen to be captured. Then start the screen capture. After the screen capture is completed, exit the program:
//Gets the position where the mouse is released end = e->pos(); //Hide box rubber->hide(); //Call screenshot function this->grapScreen(); //close this->close();
In the grapScreen() member function, we need to implement the real screenshot. Here, we use the copy function of QPixmap to obtain a sub picture of the original desktop picture (note that it is not the darkened bg). The prototype of the copy function is as follows:
QPixmap QPixmap::copy ( int x, int y, int width, int height ) const
As long as the initial position (x, y) and image size (width, height) are provided, we can obtain the image to be captured. The initial position is the start or end coordinate. The size can be calculated by using start and end:
int width = abs(start.x() - end.x()); int height = abs(start.y() - end.y()); int x = start.x() < end.x() ? start.x() : end.x(); int y = start.y() < end.y() ? start.y() : end.y();
Then call QPixmap save to save the picture.
The above is an explanation of the core functions of the screenshot. I haven't explained many details. Let's see the code and understand it by ourselves.
5, code implementation
- screenShot.h main window class declaration
#ifndef _SCREEN_SHOT_H_ #define _SCREEN_SHOT_H_ #include <QWidget> #include <QRubberBand> #include <QLabel> #include <QScreen> #include <QPoint> #include <QString> #include <QPalette> #include <QFileDialog> #include <QDesktopServices> #include <QPixmap> #include <QImage> class screenShot : public QWidget{ Q_OBJECT public: screenShot(QWidget *parent = 0); void mouseMoveEvent(QMouseEvent *e); void mousePressEvent(QMouseEvent *e); void mouseReleaseEvent(QMouseEvent *e); void mouseDoubleClickEvent(QMouseEvent *event); void keyPressEvent(QKeyEvent *e); void setLabel(); void changeLight(int x, int y, int width, int height, double bright); void paintEvent(QPaintEvent *event); void show(); void grapScreen(); ~screenShot(); private: QRubberBand *rubber; QLabel *label; QPoint start; QPoint end; QPixmap whole_window; QPixmap image; QImage *info; QLabel *info_label; QImage bg; int s_height; int s_width; QPushButton *button; }; #endif //screenShot.h
- screenShot. Implementation of CPP main window class
#include "screenShot.h" #include <QtGui> screenShot::screenShot(QWidget *parent) :QWidget(parent), rubber(NULL), label(new QLabel("")), start(QPoint(0, 0)), end(QPoint(0, 0)), whole_window(QPixmap::grabWindow(QApplication::desktop()->winId())), image(QPixmap()), info(new QImage("infoa.png")), info_label(new QLabel("")), s_height(0), s_width(0), button(new QPushButton()) { //This statement solves the Chinese garbled code QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); //QPixmap is converted to QImage here //Because only QImage can set three primary colors whole_window.save("temp.png"); bg = QImage("temp.png"); QFile *temp = new QFile("temp.png"); temp->remove(); changeLight(0, 0, bg.width(), bg.height(), 0.6); //Darken the picture //Get desktop size QDesktopWidget *d_widget = QApplication::desktop(); QRect d_rect = d_widget->screenGeometry(); s_width = d_rect.width(); s_height = d_rect.height(); //Set window size to desktop size this->resize(s_width, s_height); //This button is the prompt for the user to open the program for the first time button->setIcon(QIcon("infob.png")); button->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); button->setIconSize(QSize(211, 123)); button->setGeometry(s_width / 2, 0, 211, 123); button->show(); //After the user clicks the button, the button disappears connect(button, SIGNAL(clicked()), button, SLOT(close())); //The window and coordinate information are placed at the top of the entire screen label->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); this->setWindowFlags(Qt::FramelessWindowHint); } void screenShot::changeLight(int x, int y, int width, int height, double bright){ int red,green,blue; for(int i = x; i < width; i++){ for(int j = y; j < height; j++){ //All three primary colors are multiplied by a multiple to darken the screen red = qRed(bg.pixel(i, j)) * bright ; green = qGreen(bg.pixel(i, j)) * bright; blue = qBlue(bg.pixel(i, j)) * bright; bg.setPixel( i, j, qRgb(red, green, blue)); } } } void screenShot::mousePressEvent(QMouseEvent *e){ if(e->button() == Qt::LeftButton){ //Initialization prompt box info_label->setPixmap(QPixmap::fromImage(*info)); info_label->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); info_label->move(QPoint(e->pos().x() + 30, e->pos().y() + 30)); info_label->show(); button->close(); if(!rubber){ //Initialization box rubber = new QRubberBand(QRubberBand::Line, this); } //Display box rubber->show(); //Record the initial mouse position start = e->pos(); //Place the box at the starting position and the size is 0x0 rubber->setGeometry(start.x(), start.y(), 0, 0); //Call this function to display the coordinates setLabel(); } else if(e->button() == Qt::RightButton) { //Right click to exit the program button->close(); info_label->close(); this->close(); } } void screenShot::mouseDoubleClickEvent(QMouseEvent *event) { // The full screen should be taken here if(event->button() == Qt::LeftButton){ button->close(); QString fileName = QFileDialog::getSaveFileName(this, tr("Save picture"), ".", tr("Image Files(*.PNG)")); //Only pictures in png format are supported here whole_window.save(fileName); info_label->close(); this->close(); } } void screenShot::mouseMoveEvent(QMouseEvent *e){ if(rubber){ info_label->move(QPoint(e->pos().x() + 30, e->pos().y() + 30)); //Record where the mouse moves end = e->pos(); //Calculate the size of the box int width = abs(end.x() - start.x()); int height = abs(end.y() - start.y()); //Calculate the location of the box int x = start.x() < end.x() ? start.x() : end.x(); int y = start.y() < end.y() ? start.y() : end.y(); //Update box location rubber->setGeometry(x, y, width, height); //changeLight(x, y, width, height, 1.25); //Change coordinate display setLabel(); } } void screenShot::mouseReleaseEvent(QMouseEvent *e){ if(rubber){ info_label->hide(); label->hide(); QMessageBox::StandardButton res = QMessageBox::question(this, "inquiry", "Do you want to save this screenshot?", QMessageBox::Yes | QMessageBox::No); if(res == QMessageBox::Yes){ //Gets the position where the mouse is released end = e->pos(); //Hide box rubber->hide(); //Call screenshot function this->grapScreen(); //close this->close(); } else { //The user does not save the picture and returns to free mode button->show(); rubber->close(); info_label->hide(); label->hide(); } } } void screenShot::grapScreen(){ int width = abs(start.x() - end.x()); int height = abs(start.y() - end.y()); int x = start.x() < end.x() ? start.x() : end.x(); int y = start.y() < end.y() ? start.y() : end.y(); //Intercepts a picture of a specified size image = whole_window.copy(x, y, width, height); QString fileName = QFileDialog::getSaveFileName(this, tr("Save picture"), ".", tr("Image Files(*.PNG)")); //Save picture bool flag = image.save(fileName); if(flag){ QMessageBox::information(this, "information", "Picture saved successfully!"); } } void screenShot::setLabel(){ int width = abs(start.x() - end.x()); int height = abs(start.y() - end.y()); int x = start.x() < end.x() ? start.x() : end.x(); int y = start.y() < end.y() ? start.y() : end.y(); //Set label content QString str = QString(" %1 x %2 ").arg(width).arg(height); label->setText(str); //Repositioning labels QRect rect(label->contentsRect()); label->move(QPoint(x, y - rect.height())); label->show(); } void screenShot::keyPressEvent(QKeyEvent *event){ this->close(); } screenShot::~screenShot(){ //... } void screenShot::show(){ QWidget::show(); } void screenShot::paintEvent(QPaintEvent *event){ QPainter painter(this); //Set bg as the background picture of the whole window painter.drawImage(0, 0, bg); }
- main.cpp
#include "screenShot.h" #include <QApplication> #include <QCoreApplication> #include <QTextCodec> #include <windows.h> int main(int argc, char *argv[]){ QTextCodec *codec = QTextCodec::codecForName("UTF-8"); QTextCodec::setCodecForTr(codec); QTextCodec::setCodecForLocale(codec); QTextCodec::setCodecForCStrings(codec); QApplication a(argc, argv); screenShot *ss = new screenShot(); ss->show(); return a.exec(); }
The above codes are for reference only!
6. Question
The following problems still exist in this procedure:
- Pictures can only be saved in png format, which is a hard injury;
- The screenshot is not overhead (to display prompt information), which makes some overhead programs unable to be intercepted, such as input method;
- Converting QPixmap into QImage object and using intermediate files is inefficient;
- The technique of multiplying all three primary colors by multiples leads to low program efficiency. On some computers with poor configuration, clicking the program may take some time to start screenshots, which may cause users to be unable to accurately intercept some video animations;
- After a single right-click exit, returning to the desktop will also produce the effect of right-click. I think this problem is caused by the lack of overhead of the window.