Electron 应用实现截图并编辑功能

Electron 应用实现截图并编辑功能

Electron 应用如何实现截屏功能,有两种思路,作为一个框架是否可以通过框架实现截屏,另一种就是 javaScript 结合 html 中画布功能实现截屏。
在初步思考之后,本文优先探索使用 Electron 实现截屏功能。作为一个成熟的框架,如果能够完成截屏,那自然是已经考虑了各种会出现的问题。
Electron 想要截屏还是要用到 desktopCapturer API。这个 API 也是用来实现录屏。
首先创建一个项目,直接 clone angular-electron。

环境

  • Angular@13.3.1
  • Electron@18.0.1
  • ngx-img-cropper@11.0.0

流程:

1.渲染进程向主进程取截屏的数据。
2.主进程获取截屏数据,并返回。
3.渲染进程取到数据后,将数据转为图片显示在页面上。
4.页面编辑图片并获取新的图片数据保存到本地。

首先在 home.component.ts 中绑定一个点击事件,向主进程发送一个消息取得录屏的初始数据:

async getScreensht() {let data = await this.electron.ipcRenderer.invoke("get-screenshot");
}

在主进程 main.ts 中,首先获取当前屏幕(可能存在多个屏幕),再取得当前屏幕的截屏数据:

先看取得截屏数据的方法:

let sources = await desktopCapturer.getSources({ types: ['screen'], thumbnailSize: thumbSize });

结果如下(只有一个屏幕数据,如果有两个屏幕,则有两条数据,依次类推):

 [{name: 'Entire Screen',id: 'screen:0:0',thumbnail: NativeImage {toPNG: [Function: toPNG],toJPEG: [Function: toJPEG],toBitmap: [Function: toBitmap],getBitmap: [Function: getBitmap],toDataURL: [Function: toDataURL],...},display_id: '2528732444',appIcon: null}
]

这个结果中有一个参数 display_id,代表着对应的屏幕。那么怎么知道截屏哪个屏幕呢?需要利用鼠标点击事件,鼠标在哪个屏幕点击则截屏哪个屏幕。
鼠标点击位于当前屏幕的窗口,方法如下,通过 BrowserWindow 找到聚焦的窗口,再根据位置判断当前窗口位于哪个屏幕:

// 获取当前窗口所在屏幕
function getCurrentScreen() {let focusedWindow = BrowserWindow.getFocusedWindow();let currentBounds =  focusedWindow.getBounds();let currentDisplay = screen.getAllDisplays().find((display) => {return (currentBounds.x >= display.bounds.x &&currentBounds.x < display.bounds.x + display.bounds.width &&currentBounds.y >= display.bounds.y &&currentBounds.y < display.bounds.y + display.bounds.height);});return currentDisplay;
}

以上方法返回的结果如下,可以看到其中的 id 参数与上文中的 display_id 一致。
由此可以从 desktopCapturer.getSources() 返回的多个数据中找到当前点击的屏幕。

{id: 2528732444,bounds: { x: 0, y: 0, width: 1920, height: 1080 },workArea: { x: 0, y: 0, width: 1920, height: 1040 },accelerometerSupport: 'unknown',...
}

遗憾的是在后续的测试中,竟然存在部分设备返回 currentDisplay 中的 id 参数为 “”(空字符串)。
这样,无法通过 display_id 与 id 的一一对应,而确定截取的是哪个屏幕。
为什么会出现这种情况?在 github 上 electron 的代码库中有此讨论。
请看这里 desktopCapturer display_id is empty string

根据讨论,另一种方法为下,

function getCurrentScreen() {let currentBounds = win.getBounds();let currentDisplay = screen.getDisplayNearestPoint({ x: currentBounds.x, y: currentBounds.y });let allDisplays = screen.getAllDisplays();let currentDisplayIndex = allDisplays.findIndex((display) => {return display.id === currentDisplay.id});return { 'screen_index': currentDisplayIndex };;
}

那么梳理一下流程:渲染进程响应一个点击事件,向主进程发送一个消息,获取当前屏幕的截屏数据:

// 渲染进程
let data = await this.electron.ipcRenderer.invoke("get-screenshot");// 主进程
ipcMain.handle('get-screenshot', async (e, args) => {let current_screen = getCurrentScreen();  // 取得当前屏幕let primaryDisplay = screen.getPrimaryDisplay();// 这里的 primaryDisplay.size 由于缩放的原因可能与系统设置的分辨率不一样, 再乘上缩放比 scaleFactorlet reality_width = primaryDisplay.size.width * primaryDisplay.scaleFactor;let reality_height = primaryDisplay.size.height * primaryDisplay.scaleFactor;let thumbSize = { width: reality_width, height: reality_height };let source = await getDesktopCapturer(current_screen, thumbSize); // 取得当前屏幕截屏数据if (source) {return source;}
});async function getDesktopCapturer(current_screen, thumbSize) {let screenName = current_screen['screen_index'] + 1;let screen_names = [];screen_names.push('Screen ' + screenName);  // 中文为 `screen_names.push('屏幕 ' + screenName);`screen_names.push('Entire Screen');  // 中文为 `screen_names.push('整个屏幕');`// 以 thumbSize 屏幕分辨率取得所有屏幕截屏数据,如果 types 设置为 ['screen', 'window'] 同时可以获取各个窗口的截屏数据let sources = await desktopCapturer.getSources({ types: ['screen'], thumbnailSize: thumbSize });// 如果只有一个屏幕,则 name 为'整个屏幕',如果有两个及以上屏幕,则 name 为 '屏幕 1' 和 '屏幕 2'if (sources) {for (let source of sources) {if (screen_names.indexOf(source.name) != -1) {  // 通过 name 确定屏幕return source;}}}
}

渲染进程中取到的截屏数据如下:

{name: 'Entire Screen',id: 'screen:0:0',thumbnail: NativeImage {toPNG: [Function: toPNG],toJPEG: [Function: toJPEG],toBitmap: [Function: toBitmap],getBitmap: [Function: getBitmap],toDataURL: [Function: toDataURL],...},display_id: '2528732444',appIcon: null
}

thumbnail 为一个对象,通过其中的 toPNG、toJPG、toDataURL 等方法可以将数据转为 PNG、JPG 等格式。
例如以下转为 dataURL,即 base64 编码格式,以便在 web 中显示在 img 标签中:

let data = await this.electron.ipcRenderer.invoke("get-screenshot");
let image_url = data.thumbnail.toDataURL();

又或者在主进程中先转为 PNG 格式 let png_data = data.thumbnail.toPNG();
再使用 fs 模块直接保存到本地 fs.writeFileSync('D:\\1.png', png_data);

在渲染进程中得到了截屏数据,然后就是显示和编辑。

这里选取 ngx-img-cropper 插件。安装 npm i ngx-img-cropper@11.0.0 --save,由于本项目使用 Angular@13.3.1 所以使用 v11.0.0 版本。
ngx-img-cropper 教程。

在 module.ts 中导入 import { ImageCropperModule } from 'ngx-img-cropper';

然后根据教程中 Customizing Image cropper 一节内容这里做如下修改:

home.conponent.html 文件内容如下,去掉多余的选择文件和预览显示,留下编辑部分,再加上三个 button,用于获取截屏,清除截屏,和保存结果。

<div class="container"><div style="display: flex;"><button (click)="getScreensht()">get</button><button (click)="clear()">clear</button><button (click)="save()">save</button></div><img-cropper #cropper [image]="image_data" [settings]="cropperSettings"></img-cropper>
</div>

home.component.ts 文件修改如下,首先修改 constructor 中的内容,

this.cropperSettings = new CropperSettings();
this.cropperSettings.preserveSize = true;  // 不缩放裁剪图像 以裁剪大小保存
this.cropperSettings.keepAspect = false;  // 不保持裁剪图片纵横比
this.cropperSettings.noFileInput = true;  // 不要 input 标签
this.cropperSettings.cropperDrawSettings.strokeWidth = 2;  // 选择框边框宽度
this.cropperSettings.cropperDrawSettings.strokeColor = '#1296db';  // 选择框边框颜色
this.cropperSettings.cropperDrawSettings.fillColor = '#fff';  // 角选择块颜色
this.cropperSettings.markerSizeMultiplier = 1;  // 角选择块大小
this.cropperSettings.canvasWidth = 960;  // 画布宽
this.cropperSettings.canvasHeight = 540;
this.cropperSettings.width = 960;  // 初始选择框的宽
this.cropperSettings.height = 540;
this.data = { image: '' };

以上配置参数与页面样式或保存图片相关,添加了部分注释,点击 get button 对应的代码如下,首先是向主进程取得数据,转换后赋值。

async getScreensht() {let data = await this.electron.ipcRenderer.invoke("get-screenshot");let image_url = data.thumbnail.toDataURL();this.data['image'] = image_url;let image: any = new Image();image.src = image_url;this.cropper.setImage(image);
}

此时页面如下图显示:

在这里插入图片描述

这时拖动四个角可以选择截图区域,拖动中间图标可以移动选择截取的区域,点击 clear 清除页面。

clear() {this.cropper.reset();
}

点击 save button,则会将图片保存,保存图片方法如下,首先是取得截取的数据,再发送到主进程并重置页面。

save() {let base64Data = this.data['image'];if (base64Data) {this.electron.ipcRenderer.send('save-screenshot', {data: base64Data});this.clear();}
}

主进程接收到数据后,处理数据,去除 base64 文件编码信息部分,再通过 fs.writeFileSync() 方法保存本地。

ipcMain.on('save-screenshot', (e, args) => {let temp_file = "C:\\temp\\test.png"; // 文件路径let base64Data = args['data'].replace(/^data:image\/png;base64,/, '');let imageBuffer = Buffer.from(base64Data, 'base64');fs.writeFileSync(temp_file, imageBuffer);
});

到此即可将截屏数据显示再页面上,编辑后保存到本地。不过 ngx-img-cropper 这个插件的功能较少,暂时只能编辑大小。
CropperSettings 还有一些其他的参数,可以看 ngx-img-cropper 教程,centerTouchRadius 可以设置拖动图标的范围,默认是图标所在区域的一小部分。
一些问题,如果编辑图片的窗口是动态的,则 this.cropperSettings.canvasWidth = 960; 这些设置宽高的参数可以在 ngOnInit() 初始化中取得参数后设置。
当前截图类似与 QQ 聊天窗口中的屏幕截图按钮,会将主窗口一同截取。如果想实现 QQ 截图快捷键的操作(不截取聊天窗口,本项目是主窗口),
一种办法是在通过 desktopCapturer.getSources() 取得屏幕资源数据前最小化(minimize 方法)主窗口。并在资源数据返回到渲染进程时,再显示(show 方法)主窗口。
需要注意,要先判断主窗口最小化,再取数据,因为 minimize 需要等待时间才能获取数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/65787.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023年10款常用的Mac工具合集

Typora Typora 是一款由 Abner Lee 开发的轻量级 Markdown 编辑器&#xff0c;与其他 Markdown 编辑器不同的是&#xff0c;Typora 没有采用源代码和预览双栏显示的方式&#xff0c;而是采用所见即所得的编辑方式&#xff0c;实现了即时预览的功能&#xff0c;但也可切换至源代…

通达OA SQL注入漏洞【CVE-2023-4165】

通达OA SQL注入漏洞【CVE-2023-4165】 一、产品简介二、漏洞概述三、影响范围四、复现环境POC小龙POC检测工具: 五、修复建议 免责声明&#xff1a;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损…

Offset Explorer

Offset Explorer 简介下载安装 简介 Offset Explorer&#xff08;以前称为Kafka Tool&#xff09;是一个用于管理和使Apache Kafka 集群的GUI应用程序。它提供了一个直观的UI&#xff0c;允许人们快速查看Kafka集群中的对象以及存储在集群主题中的消息。它包含面向开发人员和管…

时序预测 | MATLAB实现EEMD-LSTM、LSTM集合经验模态分解结合长短期记忆神经网络时间序列预测对比

时序预测 | MATLAB实现EEMD-LSTM、LSTM集合经验模态分解结合长短期记忆神经网络时间序列预测对比 目录 时序预测 | MATLAB实现EEMD-LSTM、LSTM集合经验模态分解结合长短期记忆神经网络时间序列预测对比效果一览基本介绍模型搭建程序设计参考资料 效果一览 基本介绍 时序预测 | …

【设计模式】观察者模式

观察者模式是一种行为型设计模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;其所有依赖者都会收到通知并自动更新。 当对象间存在一对多关系时&#xff0c;则使用观察者模式&#xff08;Observer Pattern&#xff09;。比如…

VR全景乡村旅游浇灭乡愁,近距离体验自然之美

说起乡愁&#xff0c;可能每位漂泊的游子都有所感受&#xff0c;在外漂泊数十载&#xff0c;每到佳节倍思亲&#xff0c;家乡的一草一木都浮现在脑海中&#xff0c;满载着儿时的回忆。为了留住那抹儿时回忆&#xff0c;VR全景助力数字化乡村建设。 乡村振兴是国家的重大战略&am…

简绘ChatGPT支持Midjourney绘图 支持stable diffusion绘图

简绘支持Midjourney绘图和stable diffusion绘图。 这意味着简绘具备Midjourney绘图和stable diffusion绘图功能的支持。

运维监控学习笔记2

硬件监控&#xff1a; 1&#xff09;使用IPMI 2&#xff09;机房巡检 路由器和交换机&#xff1a; 使用SNMP&#xff08;简单网络管理协议&#xff09;进行监控。 Linux 安装snmp&#xff1a; yum install -y net-snmp net-snmp-utils 说明&#xff1a;net-snmp是安装在snm…

在CMamke生成的VS项目中插入程序

在主文件夹的CMakeLists.tex中加入SET(COMPILE_WITH_LSVM OFF CACHE BOOL "Compile with LSVM") 再添加IF(COMPILE_WITH_LSVM) MESSAGE("Compiling with: LSVM") ADD_DEFINITIONS(-DCOMPILE_WITH_LSVM) ADD_SUBDIRECTORY(LSVM) LIST(APPEND SRC LSVM_wrap…

3.解构赋值

解构赋值是一种快速为变量赋值的简洁语法&#xff0c;本质上仍然是为变量赋值。 3.1数组解构 数组解构是 将数组的单元值快速批量赋值给一系列变量 的简洁语法 1.基本语法: &#xff08;1&#xff09;赋值运算符左侧的[ ]用于批量声明变量&#xff0c;右侧数组的单元值将被赋…

《Zookeeper》源码分析(九)之选举通信网络

在上一篇文章中讲到QuorumCnxManager&#xff0c;它负责zookeeper服务器在选举期间最底层的网络通信&#xff0c;整个网络涉及到的类如下&#xff1a; 整个网络建立的过程如下&#xff1a; 选举前创建好QuorumCnxManager实例&#xff0c;并在QuorumCnxManager构造函数中创建好…

【Linux操作系统】深入理解Linux系统编程中的传入参数、传出参数和传入传出参数

在Linux系统编程中&#xff0c;函数的参数扮演着至关重要的角色。参数的传递方式可以分为传入参数、传出参数和传入传出参数。本文将详细解释这三种参数的概念、特点以及如何使用它们来实现灵活和高效的函数调用和数据传递。 文章目录 1. 解释和举例1.1 传入参数&#xff08;i…