從0到1,手把手帶你開發截圖工具ScreenCap------001實現基本的截圖功能

2023-12-07 21:00:58

ScreenCap---Version:001

說明

  • 從0到1,手把手帶你開發windows端的截圖軟體ScreenCap
  • 當前版本:ScreenCap---001
  • 支援全螢幕截圖
  • 支援滑鼠拖動截圖區域
  • 支援拖拽截圖
  • 支援儲存全螢幕截圖
  • 支援另存截圖到其他位置

警告

  • 注:博主所有資源永久免費,若有幫助,請點贊轉發是對我莫大的幫助
  • 注:博主本人學習過程的分享,參照他人的文章皆會標註原作者
  • 注:本人文章非盈利性質,若有侵權請聯絡我刪除
  • 注:獲取資源或者諮詢問題請聯絡Q:2950319782
  • 注:博主本人很菜,文章基本是二次創作,大佬請忽略我的隨筆
  • 注:我會一步步分享實現的細節,若仍有問題聯絡我

GitHub

  • 倉庫master下的ScreenCap專案
  • 若您無法正常存取,每次專案的資源會隨文章一同釋出,下載壓縮包即可,永久免費
  • 壓縮包可能較GitHub更新不及時,請諒解

開發環境

  • win10系統
  • 編譯器qtcreator4.11.1
  • QT版本:5.14.2
  • C++11

問題解決

需求

  • 提供開始截圖的按鈕,點選開始截圖
  • 在截圖介面提供右鍵選單選擇
  • 選單實現儲存當前的截圖
  • 儲存全螢幕截圖
  • 截圖另存為
  • 全螢幕截圖另存為
  • 退出截圖
  • 滑鼠可以拖拽截圖區域
  • 圖片屬性實時計算

結構

思路

screencapwidget

  • 首先需要建立頁面ScreenCapWidget,提供開始截圖,按鍵設定,預設位置的按鈕
  • 首先實現開始截圖的功能,這裡不能直接在視窗執行緒實現,需要單獨建立一個screenwidget類實現截圖的主要操作
  • 獲取到screenwidget的範例後,應該處理截圖的邏輯了,建立範例的時候直接呼叫screenwidget父類別widget的showFullScreen函數,將screenwidget以全螢幕的方式顯示出來,整個螢幕是當前截圖的操作區域,遮擋其他操作,這裡我們重寫一下screenwidget的showEvent事件

screenwidget

  • 而這個screenwidget類不應該一直存在,應該是呼叫開始截圖的時候才開始建立,這裡為了保證同一時刻只有一個screenwidget類建立,應該使用單例模式,確保只有一個範例
  • screenwidget建立的時候不需要ui檔案,這裡我們只需要使用widget裡的繪圖事件和選單功能,自己使用程式碼實現
  • 在標頭檔案裡首先維持一個靜態的QScopedPointer物件self,用於實現單例模式
  • 定義一個公共的靜態介面Instance以實現其他類來生成screenwidget物件
  • 下面來實現類的預設建構函式,提供選單功能,實現儲存當前的截圖,儲存全螢幕截圖,截圖另存為,全螢幕截圖另存為,退出截圖的功能
  • 因為screencapwidget呼叫其fullShowScreen函數,這裡重寫showEvent函數
  • showEvent函數中,直接獲取當前主螢幕的全螢幕影象儲存在fullScreen中,為提示使用者截圖開始了,這裡獲取到全螢幕物件後,模糊處理全螢幕,維持一個背景值bgScreen實現背景處理
  • 截圖介面的互動邏輯等會再實現,先處理關鍵的部分,建立一個myscreen類,實現截圖實現的資料主要邏輯
  • 重寫完showEvent後,已經獲取到全螢幕影象了,需要開始處理部分截圖了,即處理滑鼠事件,首先處理滑鼠按下press事件,第一次按下的位置就是起始位置,再根據此時myscreen的STATUS處理對應的事件
  • 處理滑鼠移動的事件,如果還在myscreen還在選擇狀態,那麼移動完的位置就是截圖的結束位置,myscreen在移動狀態,那麼計算偏移量減去移動開始時候的起始位置movPos即可,將偏移量傳入myscreen的move函數中,計算move後的截圖區域
  • 主要的滑鼠事件處理完了,下面處理release和右鍵事件
  • 滑鼠事件處理完了之後,要截圖的影象的區域我們已經知道了,下面重寫paint事件

myscreen

  • 該類主要實現對截圖的資料計算,來給screenwidget重寫事件提供詳細的資料
  • 這裡的類不需要視窗檔案,建立純粹的cpp類即可
  • 需要獲得從screenwidget類傳入的qsize引數,這裡使用帶qsize引數的建構函式
  • 首先截圖需要維護螢幕長和寬的值,maxHeight和maxWidth,這裡的資料應該是誰呼叫誰能獲取,全部設定為私有屬性,還需要設定其getWidth和getHeight方法
  • 還需要維持截圖區域的左上角和右下角的point值leftUpPos和rightDownPos,並設定getLeftUp和getRightDown方法
  • 處理滑鼠事件的時候,需要判斷當前截圖的狀態,維持列舉值STATUS,儲存選擇截圖區域,拖拽截圖,
  • 這裡需要實現判斷滑鼠是否在現有的截圖區域內isInArea和計算移動後的截圖位置的move函數

其他功能

關鍵程式碼

注:關鍵程式碼只負責解釋各部分的邏輯關係,詳解看程式碼註釋

  • screencapwidget處理開始截圖的功能,建立screenwidget的唯一範例,並顯示全螢幕視窗

    //ScreenWidget全螢幕顯示
        ScreenWidget::Instance()->showFullScreen();
    
  • 與showFullScreen相關的screenwidget的重寫showEvent事件

    //重寫視窗被顯示的事件
    void ScreenWidget::showEvent(QShowEvent *)
    {
        //設定初始位置
        QPoint point(-1,-1);
        myscreen->setStart(point);
        myscreen->setEnd(point);
    
        //獲取當前螢幕物件
        QScreen* pscreen = QApplication::primaryScreen();
        //呼叫QScreen的grabwindow進行全螢幕截圖
        *fullScreen = pscreen->grabWindow(0,0,0,myscreen->getWidth(),myscreen->getHeight());
    
        //設定透明度實現模糊背景
        QPixmap pix(myscreen->getWidth(),myscreen->getHeight());
        pix.fill((QColor(160,160,160,200)));
        bgScreen = new QPixmap(*fullScreen);
        QPainter p(bgScreen);
        p.drawPixmap(0,0,pix);
    }
    
  • screenwidget實現單例模式的主要程式碼

  • //定義單例模式,確保截圖的時候只能有一個
    ScreenWidget* ScreenWidget::Instance()
    {
        //還沒有建立範例
        if(self.isNull())
        {
            //加把鎖,只能有一個執行緒存取
            static QMutex mutex;
            //自動加解鎖
            QMutexLocker locker(&mutex);
            //再次判斷有沒有範例,防止等待的時間中有執行緒獲取到範例了
            if(self.isNull())
            {
                self.reset(new ScreenWidget);
            }
        }
        return self.data();
    
    }
    
    
  • screenwidget提供的選單功能

  • //建立一個選單檔案
        menu = new QMenu(this);
        //新增選單的功能
        menu->addAction("儲存當前的截圖",this,SLOT(saveScreen()));
        menu->addAction("儲存全螢幕截圖",this,SLOT(saveFullScreen()));
        menu->addAction("截圖另存為",this,SLOT(saveScreenOther()));
        menu->addAction("全螢幕截圖另存為",this,SLOT(saveFullOther()));
        menu->addAction("退出截圖",this,SLOT(hide()));
    
  • screenwidget維持myscreen的類,並在screenwidget的建構函式中範例化myscreen類,傳入當前螢幕的大小,二者同步生成

    myScreen* myscreen;
    
     //獲取螢幕大小
        myscreen = new myScreen(deskGeometry.size());
    
  • 獲取到當前螢幕的qrect物件,呼叫size函數獲取螢幕的size值,使用宏展開式,不單獨處理了,需要的時候直接綻開計算

    #define deskGeometry qApp->primaryScreen()->geometry()
    
  • 處理圖片移動

    void myScreen::move(QPoint p)
    {
        //計算move後的四個點座標
        int lx = leftUpPos.x() + p.x();
        int ly = leftUpPos.y() + p.y();
        int rx = rightDownPos.x() + p.x();
        int ry = rightDownPos.y() + p.y();
        //確保移動後的截圖不會超出螢幕範圍
        if(lx < 0)
        {
            lx = 0;
            rx -= p.x();
        }
        if(ly < 0)
        {
            ly = 0;
            ry -= p.y();
        }
        if(rx > maxWidth)
        {
            rx = maxWidth;
            lx -= p.x();
        }
        if(ry > maxHeight)
        {
            ry = maxHeight;
            ly -= p.y();
        }
    
        //更新移動後的值
        leftUpPos = QPoint(lx,ly);
        rightDownPos = QPoint(rx,ry);
        startPos = leftUpPos;
        endPos = rightDownPos;
    }
    
  • 處理滑鼠press

    void ScreenWidget::mousePressEvent(QMouseEvent *e)
    {
        int status = myscreen->getStatus();
        //選擇區域的狀態
        if(status == myScreen::SELECT)
        {
            //把滑鼠按下的位置設定為開始位置
            myscreen->setStart(e->pos());
        }
        //拖拽截圖
        else if(status == myScreen::MOV)
        {
            //滑鼠不在截圖的區域內,是要重新選擇截圖區域
            if(myscreen->isInArea(e->pos()) == false)
            {
                //新按下的位置設定為開始位置,並重置狀態為選擇
                myscreen->setStart(e->pos());
                myscreen->setStatus(myScreen::SELECT);
            }
            //在截圖區域內,是要拖拽截圖
            else
            {
                //開始移動的起始位置就是現在滑鼠按下的位置
                movPos = e->pos();
                this->setCursor(Qt::SizeAllCursor);
            }
        }
        this->update();
    }
    
  • 處理滑鼠move

    void ScreenWidget::mouseMoveEvent(QMouseEvent *e)
    {
        //在選擇狀態
        if(myscreen->getStatus() == myScreen::SELECT)
        {
            myscreen->setEnd(e->pos());
        }
        //在移動狀態
        else if(myscreen->getStatus() == myScreen::MOV)
        {
            //計算滑鼠偏移量
            QPoint p(e->x() - movPos.x(),e->y() - movPos.y());
            myscreen->move(p);
            movPos = e->pos();//儲存上一次滑鼠的位置
        }
        //觸發視窗的更新,重新繪製螢幕截圖和矩形框
        this->update();
    }