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

news2025/1/19 6:53:29

需求

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

在这里插入图片描述

实现

这里要重点讲下关于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>

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

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

相关文章

openEuler学习——部署MGR集群

本文介绍如何利用GreatSQL 8.0.25构建一个三节点的MGR集群。 1.安装准备 IP端口角色192.168.20.1103306mgr1192.168.20.1113306mgr2192.168.20.1123306mgr3 配置hosts解析 [rootMGR1 ~]# cat >> /etc/hosts << EOF > 192.168.20.110 MGR1 > 192.168.20.1…

小程序商城营业执照办哪种类型的?

在数字化浪潮的推动下&#xff0c;越来越多的商家选择通过微信小程序商城来拓展线上业务。但在启动小程序商城之前&#xff0c;有一项关键性的准备工作不可忽视——那就是营业执照的办理。本文将为您详细解读小程序商城营业执照的办理类型及相关流程步骤&#xff0c;帮助您顺利…

虚拟机时间同步主机

1.查看是否设置同步 2.查看时区 date -R 0800 表示时区东八区 明显不对 执行指令&#xff1a; tzselect &#xff1b;找到亚洲-中国-北京 3.覆盖一下文件 复制文件到 /etc/localtime 目录下&#xff1a;#sudo cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 4.重现查…

【DAY08 软考中级备考笔记】机组:计算机组成和数据转换

机组&#xff1a;计算机组成和数据转换 3月2日 – 天气&#xff1a;晴 1. 计算机的基本组成结构 计算机的硬件由运算器&#xff0c;控制器&#xff0c;存储器&#xff0c;输入和输出设备组成其中&#xff0c;控制器和运算器成为CPU控制器又分为了内部存储器和外部存储器。内部…

Python与FPGA——膨胀腐蚀

文章目录 前言一、膨胀腐蚀二、Python实现腐蚀算法三、Python实现膨胀算法四、Python实现阈值算法五、FPGA实现腐蚀算法总结 前言 腐蚀是指周围的介质作用下产生损耗与破坏的过程&#xff0c;如生锈、腐烂等。而腐蚀算法也类似一种能够产生损坏&#xff0c;抹去部分像素的算法。…

FX110网:判断行情是真突破还是假突破?用这几招就够了

众所周知&#xff0c;在交易过程中&#xff0c;趋向线的突破对买入、卖出时机等选择具有重要的分析意义。因此&#xff0c;搞清趋势线何时突破&#xff0c;是有效的突破还是非有效的突破&#xff0c;于投资者而言是至关重要的。 本文将提供一些对于趋向线突破的判断方法和市场原…

学习Java的第三天

如何使用IDEA工具编写Java语言 上一节课已经讲过了&#xff0c;如何使用文本文档写出代码并在管理员控制台打印出来 接下来给大家分享的是使用IntelliJ IDEA工具 一、如何将IntelliJ IDEA设置成中文 1、点击右上角的图标&#xff0c;有人的图标会不一样&#xff0c;但位置是…

停工待料,责任真的全在PMC吗?天行健深度剖析背后的原因

在现代制造业中&#xff0c;停工待料的现象时有发生&#xff0c;这不仅影响了生产进度&#xff0c;还增加了企业的运营成本。很多人会自然而然地将责任归咎于生产物料控制&#xff08;PMC&#xff09;部门&#xff0c;认为是他们没有做好物料计划和管理。但事实上&#xff0c;停…

【Web】浅浅地聊SnakeYaml反序列化两条常见利用链

目录 关于Yaml 关于SnakeYaml SnakeYaml反序列化利用 JdbcRowSetImpl链 ScriptEngineManager链 复现 基本原理 继续深入 关于Yaml 学过SpringBoot开发的师傅都知道&#xff0c;YAML和 Properties 文件都是常见的配置文件格式&#xff0c;用于存储键值对数据。 这里举…

公车5.0优化内容

1、首屏加载速度慢&#xff0c;原因是commons重复打包了&#xff0c;删除即可&#xff0c;首屏加载速度提升了10几秒&#xff0c;本地打包速度提升了2分钟 2、删除$dogShanXi重复方法&#xff0c;并全局替换dogShanXi方法 3、Tab打开太多页面卡死&#xff0c;设置最多打开10个 …

2024小红书商家投放策略怎么制定?广告形式有哪些

据平台的消息&#xff0c; 截止2021年11月&#xff0c;小红书月活已达到2亿。其中 有72%为90后&#xff0c;超50%来自一二线城市。如何充分把握好这一趋势&#xff0c;这就需要一些行之有效的小红书投放策略。今天我们和大家分享下2024小红书商家投放策略怎么制定&#xff0c;广…

【C++庖丁解牛】C++内存管理 | new和delete的使用以及使用原理

&#x1f4d9; 作者简介 &#xff1a;RO-BERRY &#x1f4d7; 学习方向&#xff1a;致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f4d2; 日后方向 : 偏向于CPP开发以及大数据方向&#xff0c;欢迎各位关注&#xff0c;谢谢各位的支持 目录 1. C/C内存分布2. C语…

Go程序是如何编译并运行起来的(图文详解)

Go程序是如何编译的 从hello RdrB1te开始 package main import "fmt" func main() { fmt.Println("hello RdrB1te") }不实际编译它&#xff0c;只输出它的编译过程&#xff1a; go build -n简单的编译过程分析&#xff1a; 上面的过程确认了两个…

打印机扫描无法识别u盘怎么办?

相信不少朋友经常会有打印或者扫描的经历&#xff0c;特别是用公司的那种硕大的打印机。 打印的时候还好说&#xff0c;扫描的时候就老会出问题。 如果按正常的流程&#xff0c;先把文稿放在面板上&#xff0c;盖上盖儿。然后在控制屏幕上选择扫描&#xff0c;然后插入u盘。正…

查看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…

【格与代数系统】基本概念和性质

【格与代数系统】偏序关系、偏序集与全序集 格 设是一个偏序集&#xff0c;若对任意&#xff0c;的上、下确界都存在&#xff0c;则称是一个格&#xff0c;用表示格。 偏序关系偏序集 偏序集上下确界格 代数系统 若是格&#xff0c;则由格的定义&#xff0c;对任意&#xff0…

EMC测试整改:提升产品合规性和市场竞争力?|深圳比创达电子

在当前的产品研发和制造领域&#xff0c;电磁兼容&#xff08;EMC&#xff09;测试是确保产品符合法规要求并能够在各种电磁环境下正常工作的重要环节。然而&#xff0c;很多企业在进行EMC测试时可能会遇到一些问题和不合格情况&#xff0c;因此需要进行整改来提升产品的合规性…

timezoneinfo的裁剪移植之uclibc/gclibc/openwrt的最详细实战版!

1.需求背景 因为项目需要&#xff0c;产品售卖到国外各个地区&#xff0c;需要能适配各个国家的不同时区&#xff0c;一些国家可能会有多个不同时区&#xff0c;并且还存在冬夏令时问题&#xff0c;都需要做到一次性的兼容。而目前板子上可用的flash空间也已经不足200KB&#…

视频扩散模型介绍 Video Diffusion Models Introduction

视频扩散模型介绍 Video Diffusion Models Introduction Diffusion 扩散模型中的一些概念DDPMDDIMCLIPLatent DiffusionStable DiifusionLoRADreamBoothControlNet 视频生成评估标准图片层面视频层面 前人的工作Make-A-VideoAlign your Latents 开源视频生成模型ModelScopeT2V&…

MATLAB KL变换

1. 原理 KL变换步骤&#xff1a; 1.求样本X的协方差矩阵R 2.求 R的特征值λ。选取前d个较大的特征值。 3.计算d个特征值对应的特征向量&#xff0c;归一化后构成变换矩阵U。 4.对{X}中每一个X进行K-L变换&#xff0c;得到变换后向量YU’ * X&#xff0c;d维向量Y就是…