如何實現基於Electron的截圖識字App(一)

Yoki發表於2019-03-31

背景

之前看了下face++的介面,做了人臉融合簡單demo,順便瞧了瞧其他的介面,看到一個識別圖片文字的介面,突然靈機一動。

平時自己看到一些有趣的圖文,裡面的文字就想敲下來放印象筆記裡或者朋友圈裡裝一下文藝。但是關鍵這裡還要手動敲,如果截圖的時候,直接就把文字複製到剪貼簿就好了。於是就有了這個應用,實現並不複雜。大家跟我一起過一遍吧,喜歡的可以star~

開始

初始化一個electron應用

參考electron官網,打造你的第一個electron應用

$ yarn add electron -D
複製程式碼

目錄結構

├─ src
│  └─ main.js    #入口檔案,主程式
│  └─ index.html # 渲染程式的頁面
複製程式碼

package.json的scripts欄位新增以下

"start": "electron src/main.js"
複製程式碼

入口檔案

electron程式分為主程式和渲染程式。渲染程式相當於前端的UI渲染,可以類比成chrome的一個tab頁,一個tab就是一個程式。渲染程式由主程式管理,主程式相當於chrome的視窗,關閉所有tab頁面還在的那個視窗。有些功能模組只能由主程式來呼叫,渲染程式同理(比如截圖這個就只能在渲染程式)

我們的需求是

  • 只需要一個托盤叫做tray(mac右上角,win左下角),不需要開啟具體的視窗介面

如何實現基於Electron的截圖識字App(一)

  • CmdOrCtrl+Shift+V為截圖快捷鍵
const { app, BrowserWindow,  globalShortcut ,Tray,Menu,ipcMain} = require('electron')

const shell = require('electron').shell;

const path=require('path')

let win

let srcPath=path.join(__dirname,"../src")

let clip=true
//建立托盤
function createTray () {
    tray = new Tray(`${srcPath}/images/font.png`) // 指定圖片的路徑,在github裡有
    const contextMenu = Menu.buildFromTemplate([ //Menu型別有checkbox,radio,normal等
        { label: 'clip', type: 'checkbox',click(){
            clip=!clip
        },checked:true },
        { label: 'about', click(){
            //開啟預設瀏覽器
            shell.openExternal('https://github.com/yokiyokiyoki/clip-font-app');
        } },
        { label: 'exit',click(){
            app.quit()
        }}
    ])
    tray.setToolTip('圖圖識字')
    tray.setContextMenu(contextMenu)
    //註冊快捷鍵
    globalShortcut.register('CmdOrCtrl+Shift+V', captureScreen)
    globalShortcut.register('Esc', () => {
        if (win) {
            win.close()
            win = null
        }
    })
}

function createCaptureWindow() {
    // 建立瀏覽器視窗,只允許建立一個(必須得建立,因為只有渲染程式才能截圖)
    if(win)return console.info('只能有一個CaptureWindow')
    const { screen } = require('electron') //因為ready才可以引入
    let { width, height } = screen.getPrimaryDisplay().bounds
    win = new BrowserWindow({ 
        fullscreen: process.platform !== 'darwin' || undefined, // win
        width,
        height,
        x: 0,
        y: 0,
        transparent: true,
        frame: false,
        skipTaskbar: true,
        autoHideMenuBar: true,
        movable: false,
        resizable: false,
        enableLargerThanScreen: true, // mac
        hasShadow: false,
        webPreferences: {
            webSecurity: false //可以載入本地檔案,這裡不寫的話,打包後會報錯:不允許你載入本地檔案
        }
    })

    win.setAlwaysOnTop(true, 'screen-saver') // mac
    win.setVisibleOnAllWorkspaces(true) // mac
    win.setFullScreenable(false) // mac

    // 然後載入應用的 index.html。
    win.loadFile(path.join(__dirname,'../index.html'))

    // 開啟開發者工具
    win.webContents.openDevTools()

    // 當 window 被關閉,這個事件會被觸發。
    win.on('closed', () => {
        win = null
    })
}
app.on('ready', createTray)
// 當全部視窗關閉時退出。
app.on('window-all-closed', () => {
    // 在 macOS 上,除非使用者用 Cmd + Q 確定地退出,
    // 否則絕大部分應用及其選單欄會保持啟用。
    if (process.platform !== 'darwin') {
        app.quit()
    }
})

app.on('activate', () => {
    if (win === null) {
        createCaptureWindow()
    }
})

function captureScreen(){
    if(clip){
        createCaptureWindow()
    }
}
複製程式碼

編寫index.html

上面我們通過主程式loadfile開啟了index.html。這裡我們可以做一個粗淺的UI,需要有尺寸資訊,工具欄等。是基於全屏的html,為什麼要這麼做?你可以思考一下~

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>截圖</title>
  </head>
  <style>
  html, body, div {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }
  .bg {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
  }
  .mask {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.6);
  }
  .rect {
    position: absolute;
    display: node;
    z-index: 1;
}
.size-info {
    position: absolute;
    color: #ffffff;
    font-size: 12px;
    background: rgba(40, 40, 40, 0.8);
    padding: 5px 10px;
    border-radius: 2px;
    font-family: Arial Consolas sans-serif;
    display: none;
    z-index: 2;
}
.toolbar {
    position: absolute;
    color: #343434;
    font-size: 12px;
    background: #f5f5f5;
    padding: 5px 10px;
    border-radius: 4px;
    font-family: Arial Consolas sans-serif;
    display: none;
    box-shadow: 0 0 20px rgba(0, 0, 0, 0.4);
    z-index: 2;
    align-items: center;
}
.toolbar>.iconfont{
  display: inline-block;
  cursor: pointer;
}
  </style>
  <body>
    <!--背景為灰色的遮罩層-->
    <div class="bg"></div>
    <div id="mask" class="mask"></div>
    <canvas class="rect"></canvas>
    <!--尺寸資訊-->
    <div class="size-info">200*200</div>
    <!--toolbar-->
    <div class="toolbar">
      <div class="iconfont icon-close" >關閉</div>
      <div class="iconfont icon-check" >確認</div>
      <div class="iconfont icon-literacy" >識別</div>
    </div>
    <script src="./src/js/capture.js"></script> <!--現在還沒寫-->
  </body>
</html>
複製程式碼

至此我們已經可以通過npm start進行開發,通過快捷鍵CmdOrCtrl+Shift+V看到這個頁面了,並且托盤也已經現在左上角。如果報錯,請審視上述之流程。

截圖功能實現

我們把邏輯寫在capture.js裡面

electron 提供了擷取螢幕的 API(desktopCapturer),可以輕鬆的獲取每個螢幕(存在外接顯示器的情況)和每個視窗的影象資訊。可以先去了解一下~

原理(其實很簡單,就是兩個canvas):

1.通過electron提供擷取全屏生成的dataURL,存在一個特殊的canvas(姑且成為全屏canvas)裡面(dataURL和cavas的相愛相殺),再把這個dataURL賦給背景,讓空白背景圖,變成剛剛全屏的樣子(假象),再加上半透明的遮罩。

2.通過另外一個canvas(選區canvas)來製作選區,滑鼠位置來確定這個選區在全屏canvas裡的位置,然後把全屏canvas的資料導過來這個選區canvas

const {  desktopCapturer, screen } = require('electron')
const { bounds: { width, height } } = screen.getPrimaryDisplay()
const path=require('path')

const {Draw} = require(`${__dirname}/src/js/draw.js`)

desktopCapturer.getSources({
    types: ['screen'],
    thumbnailSize: {
        width, height
    }
}, async(error, sources) => {
    if (error) return console.log(error)
    let screenImgUrl = sources[0].thumbnail.toDataURL() //獲取dataURL
    
    let bg=document.querySelector('.bg')
    let rect=document.querySelector('.rect')
    let sizeInfo=document.querySelector('.size-info')
    let toolbar=document.querySelector('.toolbar')
    /** 
     * 繪製類
     * ScreenImgUrl是整個螢幕base64格式的快照
     * bg是背景dom
     * width是螢幕寬高
     * rect是選區canvas
     * sizeInfo 尺寸資訊容器
     * toolbar 工具欄
    */
    let draw=new Draw(screenImgUrl,bg,width,height,rect,sizeInfo,toolbar)
    document.addEventListener('mousedown',draw.startRect.bind(draw))
    document.addEventListener('mousemove',draw.drawingRect.bind(draw))
    document.addEventListener('mouseup',draw.endRect.bind(draw))
})
複製程式碼

Draw類寫了截圖選區是如何實現的,這裡實在有些長,可以移步github。主要就是通過滑鼠畫一個矩形,然後把全屏canvas的資料通過位置定位到具體,然後匯入。

如何實現基於Electron的截圖識字App(一)
我們如果點選工具欄中的關閉,其實是需要銷燬整個視窗的。這裡其實涉及到了渲染程式和主程式間通訊的問題,這裡需要IPC模組——IPCMainipcRenderer ~

//渲染程式傳送訊息
ipcRenderer.send('clip-page', { type: type, message: msg })

//主程式接收
ipcMain.on('clip-page', (event, {type,msg}) => {
    if(type==='close'){
        if (win) {
            win.close()
            win = null
        }
    }
})
複製程式碼

打包

我們在開發模式下試了下,覺得應該沒有問題了。這時候來打包成各平臺上的應用,供使用者實際使用~

這裡我們選用electron-builder,在package.json新建一個指令碼,同時新增一個build欄位(electron-builder自動讀取)

"scripts": {
    "start": "electron src/main.js",
    "build": "electron-builder",
},
"build":{
    //名字
    "productName": "clip-font-app",
    "appId": "Personal.DesktopApp.ClipFont.1.0.0",
    "directories": {
        //打包目錄
      "output": "dist"
    },
    "files": [
    //所有檔案
      "./**/**"
    ],
    //win下的安裝嚮導
    "nsis": {
      "oneClick": false,
      "allowElevation": true,
      "allowToChangeInstallationDirectory": true,
      "installerIcon": "src/images/icon.ico",
      "uninstallerIcon": "src/images/icon.ico",
      "installerHeaderIcon": "src/images/icon.ico",
      "createDesktopShortcut": true,
      "createStartMenuShortcut": true,
      "shortcutName": "ClipFont"
    },
    "dmg": {
      "contents": [
        {
          "x": 410,
          "y": 150,
          "type": "link",
          //是否拖到應用目錄
          "path": "/Applications"
        },
        {
          "x": 130,
          "y": 150,
          "type": "file"
        }
      ]
    },
    "mac": {
      "icon": "src/images/icon.icns"
    },
    "win": {
      "icon": "src/images/icon.ico",
      "target": [
        {
          "target": "nsis",
          "arch": [
            "ia32"
          ]
        }
      ]
    },
    "linux": {
      "icon": "src/images"
    },
    //下載源為淘寶映象,國內某些原因可能會導致失敗
    "electronDownload": {
      "mirror": "http://npm.taobao.org/mirrors/electron/"
    }
}
複製程式碼

然後我們npm run build,由於我使用的是mac,然後打包下來的是dmg~,雙擊dmg安裝了。

如何實現基於Electron的截圖識字App(一)

效果

我們來使用快捷鍵(CmdOrCtrl+Shift+V),點選識別~

如何實現基於Electron的截圖識字App(一)
然後複製到剪貼簿試試

如何實現基於Electron的截圖識字App(一)

後續

當然一個截圖工具的工具欄怎麼會那麼簡單呢?

  • 工具欄完善,會包括下載等功能
  • 支援多視窗截圖

相關文章