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.mzph.cn/news/33246.shtml

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

相关文章

森海塞尔为 CUPRA 首款纯电轿跑 SUV – CUPRA Tavascan 注入音频魅力

森海塞尔为 CUPRA 首款纯电轿跑 SUV – CUPRA Tavascan 注入音频魅力 音频专家森海塞尔携手富有挑战精神的 CUPRA&#xff0c;雕琢时代新贵车型&#xff0c;打造畅快尽兴的驾驶体验 全球知名音频专家森海塞尔与以颠覆传统、充满激情、不甘现状而闻名的汽车品牌 CUPRA 展开合作…

掌握Python的X篇_28_python包管理工具pip命令

本篇将会介绍在实际使用python中最能节省效率的内容&#xff0c;利用第三方库拿来就用。 文章目录 1. pip命令是什么2. pip相关操作2.1 list2.2 install2.3 uninstall2.4 导出和导入2.4.1 freeze命令2.4.2 “-r” 3. 国内镜像4. Python Packges Index网站 1. pip命令是什么 p…

SpringBoot复习:(33)WebMvcAutoconfiguration内部静态类WebMvcAutoConfigurationAdapter

WebMvcAutoconfiguration内部静态类WebMvcAutoConfigurationAdapter实现了WebMvcConfigurer接口&#xff0c;重写了一些方法&#xff0c;也就是默认对Spring Mvc进行了一些配置: 该静态类上有个**Import**注解&#xff1a; Import(EnableWebMvcConfiguration.class) 它的父类…

【uniapp】滚动相关

1、滚动到一定区域&#xff0c;顶部内容置换并置顶 功能&#xff1a; 当我向下滚动时&#xff0c;当关注那一行快到顶部的时候&#xff0c;把左侧区域的内容切换成右侧区域的内容&#xff0c;并置顶 原先我使用v-if来显示隐藏&#xff0c;发现会出现闪屏的现象&#xff0c;后来…

选读SQL经典实例笔记23_读后总结与感想兼导读

1. 基本信息 SQL经典实例 SQL Cookbook [美]安东尼莫利纳罗&#xff08;Anthony Molinaro&#xff09; / 人民邮电出版社 / 2018-07 / 其他 人民邮电出版社,2018年7月出版第1版&#xff0c;2021年12月出版第2版 1.1. 读薄率 1版书籍总字数827千字&#xff0c;笔记总字数30…

c++QT文件操作

1 介绍 QT的文件操作来源于其抽象基类QIODevice&#xff0c;中用于处理输入输出设备。提供了统一的接口来处理不同类型的数据源&#xff0c;如文件、套接字、缓冲区等。QIODevice 主要用于读取和写入数据&#xff0c;无论数据来自何种源头&#xff0c;都可以通过 QIODevice 统一…

【资料分享】全志科技T507-H开发板规格书

1 评估板简介 创龙科技TLT507-EVM是一款基于全志科技T507-H处理器设计的4核ARM Cortex-A53国产工业评估板,主频高达1.416GHz,由核心板和评估底板组成。核心板CPU、ROM、RAM、电源、晶振等所有器件均采用国产工业级方案,国产化率100%。同时,评估底板大部分元器件亦采用国产…

HarmonyOS SDK开放能力,服务鸿蒙生态建设,打造优质应用体验

华为开发者大会2023&#xff08;HDC.Together&#xff09;于8月4日至6日在东莞松山湖举行&#xff0c;在HarmonyOS端云开放能力技术分论坛上&#xff0c;华为为广大开发者们介绍了HarmonyOS SDK开放能力在基础开发架构、功能特性等方面的变化之处&#xff0c;通过将常见的通用能…

日常BUG——Java使用Bigdecimal类型报错

&#x1f61c;作 者&#xff1a;是江迪呀✒️本文关键词&#xff1a;日常BUG、BUG、问题分析☀️每日 一言 &#xff1a;存在错误说明你在进步&#xff01; 一、问题描述 直接上代码&#xff1a; Test public void test22() throws ParseException {System.out.p…

竞赛项目 深度学习疲劳驾驶检测 opencv python

文章目录 0 前言1 课题背景2 实现目标3 当前市面上疲劳驾驶检测的方法4 相关数据集5 基于头部姿态的驾驶疲劳检测5.1 如何确定疲劳状态5.2 算法步骤5.3 打瞌睡判断 6 基于CNN与SVM的疲劳检测方法6.1 网络结构6.2 疲劳图像分类训练6.3 训练结果 7 最后 0 前言 &#x1f525; 优…

SpringBoot在线失物招领系统

一个基于SpringBootSemanticUI的pc Web在线失物招领系统 http://localhost:8080/swzl/index 主页 http://localhost:8080/swzl/login 登录页 用户表user admin字段为true是管理员 false用户 springboot2.3 springmvc mybatis html ajax idea 或eclipse maven mys…

C++11语法笔记

文章目录 一.类中新增的默认成员函数:移动赋值和移动构造二.lambda表达式三.包装器bind函数 一.类中新增的默认成员函数:移动赋值和移动构造 二.lambda表达式 三.包装器 bind函数

使用 PyTorch 逐步检测单个对象

一、说明 在对象检测任务中&#xff0c;我们希望找到图像中对象的位置。我们可以搜索一种类型的对象&#xff08;单对象检测&#xff0c;如本教程所示&#xff09;或多个对象&#xff08;多对象检测&#xff09;。通常&#xff0c;我们使用边界框定义对象的位置。有几种方法可以…

【Fegin技术专题】「原生态」打开Fegin之RPC技术的开端,你会使用原生态的Fegin吗?(上)

前提介绍 Feign是SpringCloud中服务消费端的调用框架&#xff0c;通常与ribbon&#xff0c;hystrix等组合使用。由于遗留原因&#xff0c;某些项目中&#xff0c;整个系统并不是SpringCloud项目&#xff0c;甚至不是Spring项目&#xff0c;而使用者关注的重点仅仅是简化http调…

《golang设计模式》第二部分·结构型模式-01-适配器模式(Adapter)

文章目录 1. 概念1.1 角色1.2 应用场景1.2 类图 2. 代码示例2.1 设计2.2 代码2.3 示例类图 1. 概念 定义一个适配器&#xff0c;帮助原本不能实现接口的类“实现”该接口 1.1 角色 目标&#xff08;Target&#xff09;&#xff1a;客户端调用的目标接口 被适配者&#xff08…

phpspreadsheet excel导入导出

单个sheet页Excel2003版最大行数是65536行。Excel2007开始的版本最大行数是1048576行。Excel2003的最大列数是256列&#xff0c;2007以上版本是16384列。 xlswriter xlswriter - PHP 高性能 Excel 扩展&#xff0c;功能类似phpspreadsheet。它能够处理非常大的文件&#xff0…

微信小程序隐私协议模板

在 设置 中找到 用户隐私保护 进行更新&#xff0c;如下图&#xff1a; 具体协议补充可参考如下&#xff1a; 为了分辨用户&#xff0c;开发者将在获取你的明示同意后&#xff0c;收集你的微信昵称、头像 为了显示距离&#xff0c;开发者将在获取你的明示同意后&#xff0c;收…

dubbo之高可用

负载均衡 概述 负载均衡是指在集群中&#xff0c;将多个数据请求分散到不同的单元上执行&#xff0c;主要是为了提高系统的容错能力和对数据的处理能力。 Dubbo 负载均衡机制是决定一次服务调用使用哪个提供者的服务。 策略 在Dubbo中提供了7中负载均衡策略&#xff0c;默…

综合能源系统(6)——综合能源综合评估技术

综合能源系统关键技术与典型案例  何泽家&#xff0c;李德智主编 综合能源系统是多种能源系统非线性耦合的、多时间与空间尺度耦合的“源-网-荷一储”一体化系统&#xff0c;通过能源耦合、多能互补&#xff0c;能够实现能源的高效利用&#xff0c;并提高新能源的利用水平。对…

在idea运行python文件

在idea运行python文件 如果在idea运行python文件而没有弹出run的选项&#xff0c;则点击File->Settings…->Plugins&#xff0c;在里面搜索python&#xff0c;如果没有显示则在Maketplace进行搜索&#xff0c; 接着Install&#xff0c;然后restart