【Vue】vue3 在图片上渲染 OCR 识别后的文本框、可复制文本组件

需求

  • 后面返回解析后的文本和四角坐标,在图片上渲染成框,并且可复制。
  • 图片还可以缩放、拖拽
    在这里插入图片描述

在这里插入图片描述

实现

这里要重点讲下关于OCR文本框的处理:

因为一些文字可能是斜着放的,所有我们要特殊处理,根据三角函数来计算出它的偏转角度,从而旋转,所有下面的 handleStyle 函数有点复杂,不说明怕你看不懂😂

<template>
  <div class="preview-wrap" @mousewheel="handerMousewheel">
    <div class="preview">
      <div
        class="preview-content"
        :style="{
          top: imgConfig.imgTop + 'px',
          left: imgConfig.imgLeft + 'px',
          transform: `scale(${imgConfig.imgScale}) rotateZ(${imgConfig.imgRotate}deg)`,
        }"
        ref="previewContentRefs"
        @mousedown="handleMoveStart"
      >
        <img :src="src" @load="onImageLoaded($event)" />
        <!-- OCR 识别框 -->
        <template v-if="imgConfig.width && imgConfig.height && ocrInfo?.length">
          <div
            class="ocr-box"
            v-for="item in ocrInfo"
            :style="handleStyle(item[2])"
          >
            {{ item[0] }}
          </div>
        </template>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { reactive, ref } from 'vue';
interface Props {
  src: string;
  ocrInfo?: OCRInfoItem[];
}
const props = defineProps<Props>();
const imgConfig = reactive({
  width: 0, // 图片原始宽度
  height: 0, // 图片原始高度
  wrapWidth: 0, // 图片容器宽度
  wrapHeight: 0, // 图片容器高度
  startPageX: 0, // 按下鼠标时当前鼠标所在位置x
  startPageY: 0, // 按下鼠标时当前鼠标所在位置y
  imgTop: 0, // 图片定位置top
  imgLeft: 0, // 图片定位置left
  imgScale: 1, // 图片缩放
  imgRotate: 0, // 图片旋转
});
const previewContentRefs = ref<HTMLElement | null>(null);

const handleStyle = (axis: any) => {
// 这里为什么要处理三角形:因为一些文字是偏转的,需要特殊处理角度。
  // 处理偏斜的文字
  // 三角形的高
  const triangleY = axis[0][1] - axis[1][1];
  // 三角形的底
  const triangleX = axis[1][0] - axis[0][0];
  // 三角形的斜边
  const triangle = Math.sqrt(
    Math.abs(triangleY * triangleY) + Math.abs(triangleX * triangleX),
  );
  // sinA = 对边 / 斜边
  const sinA = triangleY / triangle;
  // 旋转角度 = asin(sinA) / π * 180
  let rotate = Math.asin(sinA) / (Math.PI / 180);
  return {
    width: ((axis[1][0] - axis[0][0]) / imgConfig.width) * 100 + '%',
    height: ((axis[3][1] - axis[0][1]) / imgConfig.height) * 100 + '%',
    top: (axis[0][1] / imgConfig.height) * 100 + '%',
    left: (axis[0][0] / imgConfig.width) * 100 + '%',
    fontSize:
      ((axis[3][1] - axis[0][1]) / imgConfig.height) * imgConfig.wrapHeight +
      'px',
    // 注意旋转正负 三角形的高大于0 旋转角度为负数
    transform: `rotate(${triangleY > 0 ? '-' : ''}${rotate}deg)`,
  };
};

// 鼠标滚轮缩放图片
const handerMousewheel = (e: any) => {
  // 鼠标没有在图片区域内就不缩放(解决多列表下拉问题)
  if (e.target.className !== 'preview') {
    // 火狐浏览器为e.detail 其他浏览器均为e.wheelDelta
    if ((e.wheelDelta > 0 || e.detail > 0) && imgConfig.imgScale < 4) {
      imgConfig.imgScale += 0.1;
    } else if ((e.wheelDelta < 0 || e.detail < 0) && imgConfig.imgScale > 0.5) {
      imgConfig.imgScale += -0.1;
    }
    // 阻止浏览器默认滚动事件
    e.preventDefault();
  }
};

const onImageLoaded = (event: any) => {
  if (previewContentRefs.value) {
    // 图片加载完成后获取图片容器的宽高
    imgConfig.wrapWidth = previewContentRefs.value.clientWidth;
    imgConfig.wrapHeight = previewContentRefs.value.clientHeight;
  }
  // 获取图片的原始宽高
  const { naturalWidth, naturalHeight } = event.target;
  imgConfig.width = naturalWidth;
  imgConfig.height = naturalHeight;
};

// 按下鼠标开始移动图片
const handleMoveStart = (e: any) => {
  // 如果不是图片就不拖动
  if (e.target.tagName !== 'IMG') {
    return;
  }
  const { pageX, pageY } = e;
  imgConfig.startPageX = pageX - imgConfig.imgLeft;
  imgConfig.startPageY = pageY - imgConfig.imgTop;
  document.addEventListener('mousemove', handleMore, false);
  document.addEventListener('mouseup', clearEvent, false);
  e.preventDefault();
};

// 移除事件
const clearEvent = () => {
  document.removeEventListener('mousemove', handleMore, false);
};

// 按住鼠标移动时的处理
const handleMore = (e: any) => {
  const { pageX, pageY } = e;
  imgConfig.imgTop = pageY - imgConfig.startPageY;
  imgConfig.imgLeft = pageX - imgConfig.startPageX;
  e.preventDefault();
};
</script>

<style scoped lang="scss">
.preview-wrap {
  width: 100%;
  height: 100%;
  overflow: hidden;
  li {
    list-style: none;
  }

  .preview {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    &-content {
      position: relative;
      transition: 0.2s transform;
      height: 100%;
      > img {
        width: auto;
        height: 100%;
        // 禁止图片拖动
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
        // 禁止拖拽
        -webkit-user-drag: none;
        -moz-user-drag: none;
        -ms-user-drag: none;
      }
      .ocr-box {
        position: absolute;
        left: 0;
        top: 0;
        background-color: rgba(255, 240, 108, 0.3);
        color: transparent;
        box-sizing: border-box;
        overflow: hidden;
        line-height: 1;
        text-align: justify; // 两端对齐
        text-align-last: justify; // 两端对齐
        &::selection {
          background-color: rgba(49, 140, 238, 0.5);
        }
      }
    }
    &-footer {
      position: absolute;
      bottom: 20px;
      left: 50%;
      transform: translateX(-50%);
      &-tools {
        display: flex;
        justify-content: center;

        li {
          margin-right: 10px;
          padding: 10px;
          border-radius: 50%;
          background: rgba(110, 110, 110, 0.7);
          cursor: pointer;
          > img {
            display: block;
            width: 30px;
            height: 30px;
          }
          &:hover {
            i {
              color: #ef544e;
            }
          }
        }
      }

      &-thumbs {
        margin-top: 20px;
        max-width: 700px;
        overflow-x: auto;
        white-space: nowrap;

        .thumb-item {
          padding: 10px;
          margin-right: 10px;
          display: inline-block;
          background: rgba(102, 102, 102, 0.7);
          border-radius: 5px;
          cursor: pointer;
          img {
            width: 60px;
            height: 60px;
            object-fit: cover;
          }
          &.active {
            background: rgba(239, 84, 78, 0.7);
          }
        }
        &::-webkit-scrollbar {
          height: 10px;
        }

        &::-webkit-scrollbar-thumb {
          border-radius: 10px;
          -webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
          background: #d2d2d2;
          cursor: pointer;
        }

        &::-webkit-scrollbar-track {
          border-radius: 10px;
          background: #fff;
        }
      }
    }
    .close-icon {
      padding: 10px;
      position: absolute;
      top: 30px;
      right: 30px;
      border-radius: 50%;
      background: rgba(110, 110, 110, 0.7);
      cursor: pointer;
      > img {
        display: block;
        width: 30px;
        height: 30px;
      }
    }
  }
}
</style>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

分布式ID生成策略-雪花算法Snowflake

分布式ID生成策略-雪花算法Snowflake 一、其他分布式ID策略1.UUID2.数据库自增与优化2.1 优化1 - 共用id自增表2.2 优化2 - 分段获取id 3.Reids的incr和incrby 二、雪花算法Snowflake1.雪花算法的定义2.基础雪花算法源码解读3.并发1000测试4.如何设置机房和机器id4.雪花算法时钟…

短剧系统开发:一种新型的娱乐方式

一、引言 随着科技的快速发展&#xff0c;人们的生活方式也在逐渐改变。在娱乐领域&#xff0c;短剧作为一种新型的娱乐方式&#xff0c;正在受到越来越多人的喜爱。短剧以其短小精悍、情节紧凑、易于观看等特点&#xff0c;迅速占领了市场。因此&#xff0c;开发一款短剧系统…

基于STC12C5A60S2系列1T 8051单片机的TM1638键盘数码管模块的数码管显示应用

基于STC12C5A60S2系列1T 8051单片机的TM1638键盘数码管模块的数码管显示应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍TM1638键盘数码管模块概述TM1638键盘数码管…

pytorch什么是梯度

目录 1.导数、偏微分、梯度1.1 导数1.2 偏微分1.3 梯度 2. 通过梯度求极小值3. learning rate3. 局部最小值4. Saddle point鞍点 1.导数、偏微分、梯度 1.1 导数 对于yx 2 2 2 的导数&#xff0c;描述了y随x值变化的一个变化趋势&#xff0c;导数是个标量反应的是变化的程度&…

NoSQL--3.MongoDB配置(Linux版)

目录 2.2 Linux环境下操作 2.2.1 传输MongoDB压缩包到虚拟机&#xff1a; 2.2.2 启动MongoDB服务&#xff1a; 2.2 Linux环境下操作 2.2.1 传输MongoDB压缩包到虚拟机&#xff1a; &#xff08;笔者使用XShell传输&#xff09; 如果不想放在如图的路径&#xff0c;删除操作…

基于springboot+vue实现学校田径运动会系统项目【项目源码+论文说明】计算机毕业设计

基于springbootvue实现学校田径运动会系统演示 摘要 随着互联网普及率的提高&#xff0c;互联网与人们日常生活的关系越来越密切&#xff0c;越来越多学校也正在着力建设自己的信息化管理系统&#xff0c;学校根据自身的发展及社会发展的需要&#xff0c;开始将传统的运动会成…

Golang模糊测试实践

模糊测试可以简单快速的自动化构建测试用例&#xff0c;尽量遍历各种可能的输入场景&#xff0c;从而保证函数代码覆盖尽可能多的边缘场景。Go原生内置了模糊测试的支持&#xff0c;如果善加利用&#xff0c;可以有效提升Go代码的质量。原文: Fuzz Testing in Golang 题图由Lex…

Hadoop配置日志的聚集——jobhistory不显示任务问题

问题&#xff1a; 一开始job history是正常的&#xff0c;配置了日志的聚集以后不管做什么任务都不显示任务&#xff0c;hdfs是正常运行&#xff0c;而且根据配置步骤都重启过了。 下面先po出日志聚集的操作步骤&#xff0c;再讲问题 1.配置yarn-site.xml cd $HADOOP_HOME/e…

0基础跨考408|一战上岸复盘及经验分享

基础阶段‼️ 王道的四本书的选择题部分要都做完、订正完。 王道的四门视频课要一轮刷完&#xff08;或者题主在B站看了其他的老师&#xff0c;这其实也是算一轮的&#xff0c;只要题主是认真学习了的&#xff0c;题主说自己不知道看什么课&#xff0c;王道就好了&#xff09;…

kibana配置 dashbord,做可视化展示

一、环境介绍 这里我使用的kibana版本为7.17版本。 语言选择为中文。 需要已经有es&#xff0c;已经有kibana&#xff0c;并且都能正常访问。 二、背景介绍 kibana的可视化界面&#xff0c;可以配置很多监控统计界面。非常方便&#xff0c;做数据的可视化展示。 这篇文章&…

【四】【SQL Server】如何运用SQL Server中查询设计器通关数据库期末查询大题

数据库学生选择1122 数据库展示 course表展示 SC表展示 student表展示 数据库学生选课1122_3 第十一题 第十二题 第十三题 第十四题 第十五题 数据库学生选课1122_4 第十六题 第十七题 第十八题 第十九题 第二十题 数据库学生选课1122_5 第二十一题 第二十二题 结尾 最后&…

恒驰上云规划实施解决方案上线华为云官网

华为云与伙伴共同打造联合解决方案 已成为更多企业的数字化转型利器 1月恒驰上云规划实施解决方案 完成上市宣讲并正式上架华为云官网 恒驰上云规划实施解决方案能力全景图&#xff1a;融合厂商云服务能力&#xff0c;一站式高效云迁移 从深入了解企业的本地IT环境、业务特点…

查看kafka消息消费堆积情况

查看主题命令 展示topic列表 ./kafka-topics.sh --list --zookeeper zookeeper_ip:2181描述topic ./kafka-topics.sh --describe --zookeeper zookeeper_ip:2181 --topic topic_name查看topic某分区偏移量最大&#xff08;小&#xff09;值 ./kafka-run-class.sh kafka.too…

Git——Upload your open store

0.default config ssh-keygen -t rsa #之后一路回车,当前目录.ssh/下产生公私钥 cat ~/.ssh/id_rsa.pub #复制公钥到账号 git config --global user.email account_email git config --global user.name account_name1. 上传一个公开仓库 查看当前分支&#xff1a; git branc…

JavaSE——基础小项目-模拟ATM系统(项目主要目标、技术选型、架构搭建、具体实现、完整代码注释)

目录 项目主要目标 技术选型 面向对象编程 使用集合容器 程序流程控制 使用常见API 系统架构搭建与欢迎页设计 Account ATM Test 用户开户功能实现 录入账户名称与性别 录入账户密码与取现额度 生成新卡号 存入账户 登录功能实现 登录后操作实现 退出账户 存…

python基础(11)《Allure报告中的组件用法》

使用 官方教程&#xff1a;https://docs.qameta.io/allure 入门 想要看到allure报告&#xff0c;需要做2个步骤&#xff1a; 1、pytest执行时关联allure&#xff1a;pytest命令带上--alluredir 结果存放目录或--alluredir结果存放目录&#xff1b; 2、打开执行报告&#xff…

通过勒索病毒攻击案例,思考勒索病毒攻击现象与趋势

前言 2019年针对企业的勒索病毒攻击越来越多&#xff0c;仿佛全球都在被勒索&#xff0c;基本上每天都会有关于勒索病毒攻击的案例被曝光&#xff0c;勒索病毒攻击已经成为全球最大的网络安全威胁&#xff0c;同时也被国际刑警组织认定为全球危害最大的网络犯罪组织活动&#…

nginx代理参数proxy_pass

proxy_pass参数用于配置反向代理&#xff0c;指定客户端请求被转发到后端服务器&#xff0c;后端地址可以是域名、ip端口URI 代理后端报错提示本地找不到CSS文件、JavaScript文件或图片 例如&#xff1a; nginx &#xff1a;10.1.74.109 后端服务&#xff1a;http://10.1.74.…

钡铼技术R40工业路由器连接智慧交通助力城市智慧化建设

随着信息技术与交通行业的深度融合&#xff0c;智慧交通作为智慧城市的重要组成部分&#xff0c;正在全球范围内加速推进。在此进程中&#xff0c;钡铼技术推出的R40工业路由器以其独特的4G WiFi一体化设计&#xff0c;成为连接智慧交通各环节&#xff0c;助力城市智慧化建设的…

C++小记 -链表

链表 文章目录 链表链表基础理论链表的类型单链表双链表循环链表 链表的存储方式链表的定义链表的操作添加节点删除节点 性能分析构建链表删除节点&#xff08;内存泄漏的坑&#xff09;1.直接移除2.使用虚拟头结点3.delete指针后&#xff0c;要将指针置为NULL&#xff01;&…