vue中,实现PC微信图片中文字选中功能

前言

昨天正好不忙,在用PC端微信聊天时,想了一想微信中已发送的图片可以直接选中图中任意的文字,后续可直接复制使用,比起其他软件(例如:QQ)做得好,用户体验起来非常丝滑;我就在想,这个功能是怎么实现的呢?我能不能用前端实现一下,光想可没用,于是我就花了一点时间完成了这个小且实用的功能

开发环境

编辑器:VsCode

Vue:3.3.4

Sass:1.63.6

TypeScript:5.0.2

Element-Plus:2.3.7

Axios:1.4.0

实现思路

1、识别图片中的文字

这里我经过查阅相关资料,了解到了:OCR ,它是一种通过扫描物理文本并将其转换为机器可读文本的技术,即 光学字符识别

这里为了前端、后端开发者能够 快速上手 ,我采用了大厂云平台的API:百度智能云 的 文字识别 API,当然,像 阿里云、腾讯云 也有对应的API,我采用的是按量计费,因为便宜,经过我几次测试花了一块多钱,也不是很便宜

当然,也有 免费的开源 的 OCR API,例如:Tesseract 等,其开源Github地址为:github.com/tesseract-o...,安装部署教程官网都有,但是没有云平台的产品方便,这里为了演示和方便就采用了云平台API的方案

调用百度云的API能返回图片中文字的 段落信息字符信息位置坐标元素高宽 等信息

2、将识别出的文字展示在图片上

这里就用绝对定位将 每字符的元素 放在原图的上层就行了,然后把 文字颜色 设置为 透明 就差不多了

3、优化字体大小和排版

识别出的文字大小可能与原图 不符合 ,需要写一个函数将其字体大小的 最大近似值 计算出来,排版的话需要根据API返回的段落信息与字符信息结合,这样排版就能和微信一样了,我这里就没做排版,直接使用的字符的位置信息定位,后续需要你们自己优化啦~

购买与应用创建

记得要去百度智能云开通: 文字识别 -> 通用场景OCR -> 通用文字识别-高精度含位置版 接口

然后进入 控制台 -> 文字识别控制台 -> 公有云服务 -> 应用列表 -> 创建应用

创建应用后,就能获取 AKSK

依赖安装

Element-Plus:npm install element-plus
Axios:npm install axios

API接口地址代理配置

ps:在 vite.config.ts 中,server 块下添加以下内容;若不添加,请求会出现 跨域错误

js 复制代码
 proxy: {
            '/baidu-api': {
                target: 'https://aip.baidubce.com/',
                changeOrigin: true,
                secure: false,
                rewrite: (path) => path.replace(/^\/baidu-api/, '')
            }
        }

创建 认证函数

ps:用于获取 鉴权签名信息

js 复制代码
init(cb: Function) {
            // 使用axios请求
            axios({
                method: 'POST',
                url: '/baidu-api/oauth/2.0/token?grant_type=client_credentials&client_id=' + '你的API Key' + '&client_secret=' + '你的Secret Key'
            })
                .then((resp: any) => {
                    // 如果 鉴权签名信息 存在,就回调其值
                    if (resp && resp.data && resp.data.access_token) {
                        cb(resp.data.access_token);
                    }
                })
                .catch((err: any) => {
                    // 如果报错,就回调空值
                    cb(null);
                });
        }

创建 OCR识别函数

1、创建页面上传组件

html 复制代码
<!-- 上传文件之前的钩子函数:doOcr -->
<el-upload class="upload-demo" drag :before-upload="doOcr">
    <!-- 文件上传图标 -->
    <el-icon class="el-icon--upload"><upload-filled /></el-icon>
    <!-- 文字说明 -->
    <div class="el-upload__text">拖拽文件到此 或 <em>点击上传</em></div>
</el-upload>

2、编写钩子函数:doOcr

js 复制代码
doOcr(file: any) { // 函数会回调用户上传的文件对象
            let app = this;
            // 创建 FileReader 对象,用浏览器于读取文件内容
            const reader = new FileReader() as any;
            // 文件读取完成时触发的回调函数
            reader.onloadend = () => {
                // 将读取的内容转为 base64编码 字符串,这是百度官方文档的要求
                const base64String = reader.result.split(',')[1];
                // 百度OCR接口调用
                app.init((token: any) => { // 调用认证函数获取鉴权签名信息
                    axios({
                        method: 'POST',
                        url: '/baidu-api/rest/2.0/ocr/v1/accurate?access_token=' + token,
                        // 设置请求头信息
                        headers: {
                            'Content-Type': 'application/x-www-form-urlencoded',
                            Accept: 'application/json'
                        },
                        // 设置请求参数
                        data: {
                            image: base64String, // 图像数据,不能超过10M
                            recognize_granularity: 'small' // 是否定位单字符位置,big:不定位单字符位置,默认值;small:定位单字符位置
                        }
                    })
                        .then((resp) => {
                            // 遍历识别结果数组
                            resp.data.words_result.forEach((obj: any) => {
                                // 执行渲染所有字符的函数
                                app.randerDoms(obj.chars);
                            });
                        })
                        .catch((err) => {
                            // 控制台打印错误
                            console.log('OCR发生错误:{}', err);
                        });
                });
            };
            // 文件读取出错时触发的回调函数
            reader.onerror = (err: any) => {
                // 控制台打印错误
                console.log('读取文件时出错:{}', err);
            };
            // 读取文件
            reader.readAsDataURL(file);
            // 停止组件上传,因为这里使用了自定义函数
            return false;
        },

创建 渲染所有字符的函数

1、创建页面的图片元素

html 复制代码
<div id="original-image">
    <img :src="img" alt="暂无图片,请上传" />
</div>

2、创建 渲染所有字符的函数

js 复制代码
randerDoms(list: any) { // 接收一个字符信息集合
            // 定义字符信息对象数据结构
            interface CharacterData {
                char: string;
                location: {
                    top: number;
                    left: number;
                    width: number;
                    height: number;
                };
            }
            // 声明字符信息集合变量
            const characters: CharacterData[] = list;
            // 获取图片包裹div的dom
            const img = document.getElementById('original-image') as HTMLImageElement;
            // 设置样式
            img.style.position = 'relative';

            // 遍历所有字符数组,对于每个字符创建一个子元素
            characters.forEach((charData) => {
                const { char, location } = charData;
                const { top, left, width, height } = location;

                // 创建子元素,并设置其宽度和高度、以及文字内容
                const charElement = document.createElement('div');
                charElement.innerHTML = char;
                charElement.style.width = `${width}px`;
                charElement.style.height = `${height}px`;

                // 设置子元素的位置
                charElement.style.position = 'absolute';
                charElement.style.left = `${left}px`;
                charElement.style.top = `${top}px`;

                // 设置文字颜色
                charElement.style.color = 'rgba(255, 0, 0, 255)';

                // 将子元素插入到父元素中
                img.appendChild(charElement);
            });
        }

3、目前效果如下

ps:文字位置 基本上都对上了,但是 字体大小不符合 原图

创建 计算字体大小的函数

js 复制代码
        /**
         * 计算字体大小的函数
         *
         * @param text 需要计算的文本内容
         * @param targetWidth 目标宽度
         * @param targetHeight 目标高度
         * @param minFontSize 最小字体大小,默认为 10
         * @param maxFontSize 最大字体大小,默认为 100
         * @param threshold 阈值,用于控制计算精度,默认为 1
         */
        calculateFontSize(
            text: string,
            targetWidth: number,
            targetHeight: number,
            minFontSize = 10,
            maxFontSize = 100,
            threshold = 1
        ): number {
            // 创建一个 div 元素作为容器,用于计算实际字体大小
            const container = document.createElement('div');
            container.style.display = 'inline-block';
            container.style.width = `${targetWidth}px`;
            container.style.height = `${targetHeight}px`;
            // 创建一个临时的 span 元素,并添加到容器内,用于显示需要计算的文本内容
            const tempElement = document.createElement('span');
            tempElement.style.display = 'inline-block';
            tempElement.style.whiteSpace = 'pre-wrap';
            tempElement.style.fontFamily = 'Arial, sans-serif';
            tempElement.innerText = text;
            container.appendChild(tempElement);
            document.body.appendChild(container);
            // 初始化字体大小的取值范围
            let fontSize = maxFontSize;
            let lowerBound = minFontSize;
            let upperBound = maxFontSize;
            // 使用二分法计算最适合的字体大小
            while (lowerBound <= upperBound) {
                fontSize = Math.floor((lowerBound + upperBound) / 2);
                tempElement.style.fontSize = `${fontSize}px`;
                // 当计算出的实际宽度或高度大于指定的目标宽度和高度时,缩小字体大小并继续计算
                if (tempElement.offsetWidth > targetWidth + threshold || tempElement.offsetHeight > targetHeight + threshold) {
                    upperBound = fontSize - 1;
                } else {
                    // 当计算出的实际宽度和高度小于等于指定的目标宽度和高度时,增大字体大小并继续计算
                    lowerBound = fontSize + 1;
                }
            }
            // 从文档中移除容器及其子元素,并返回计算出的最适合的字体大小
            document.body.removeChild(container);
            return fontSize;
        }

使用后,效果如下

ps:已经基本 接近原图字体大小 了,还有待优化,就留给你们发挥了~

将文字颜色设置为透明

js 复制代码
charElement.style.color = 'rgba(255, 0, 0, 0)';

最终效果,如下

然后基本上就能像微信那样复制图片中的文字了,但微信的排版和元素渲染逻辑更复杂,要完全达到其效果,还有待你们不断优化~

结语

如有不懂或者疑问,可在下方评论或私信我

所有百度有关的代码均参照官方文档使用,更多用法和详细内容请参照百度云API的官方文档

相关推荐
Dread_lxy3 分钟前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
龙猫蓝图2 小时前
vue el-date-picker 日期选择器禁用失效问题
前端·javascript·vue.js
peachSoda72 小时前
随手记:简单实现纯前端文件导出(XLSX)
前端·javascript·vue.js
Tttian6223 小时前
Vue全栈开发旅游网项目(11)-用户管理前端接口联调
前端·vue.js·django
龙猫蓝图4 小时前
vue el-date-picker 日期选择 回显后成功后无法改变的解决办法
前端·javascript·vue.js
ldq_sd5 小时前
node.js安装和配置教程
node.js
刘志辉5 小时前
Pure Adminrelease(水滴框架配置)
vue.js
工业互联网专业5 小时前
Python毕业设计选题:基于Django+uniapp的公司订餐系统小程序
vue.js·python·小程序·django·uni-app·源码·课程设计
我真的很困6 小时前
坤坤带你学浏览器缓存
前端·http·node.js
黄景圣6 小时前
CURD低代码程序设计
前端·vue.js·后端