鸿蒙实现在图片上进行标注

一.实现思路

现在需求是:后端会返回在这张图片上的相对位置,然后前端这边需要在图片上进行标注,就是画个框框圈起来,返回的数据里包括当前框的x,y坐标和图片大小,大体思路就是使用canvas绘制,使用鸿蒙的stack将图片和canvas进行重合,在canvas上进行标注,使他看起来和在图片上是一样的

1.先通过axios进行图片上传

2.传完以后会返回当前需要标注的数据

3.使用canvas进行绘制,绘制的内容包括(框,两行文字)

实现效果:

二.代码

1.进行图片选择

(这里因为支持多张上传,所以有多张绘制,那么canvas的实例就不能是一个,所以这里在上传的时候,每一张图片就创建一次实例,canvas不支持一个实例多次绘制)

    Button('选择并上传图片')   .position({ x: 100, y: 685 }).onClick(async () => {// 创建 图片选择对象const photoViewPicker = new picker.PhotoViewPicker();// 调用 select 方法,传入选项对象photoViewPicker.select(photoSelectOptions).then(async res => {const context = getContext(this)res.photoUris.forEach((item)=>{// this.str= itemlet  settings: RenderingContextSettings = new RenderingContextSettings(true)let context1: CanvasRenderingContext2D = new CanvasRenderingContext2D(settings)let offCanvas: OffscreenCanvas = new OffscreenCanvas(600, 600)this.arr.push({ url:item,context:context1 })// 三、拷贝文件到缓存目录// 将文件保存到缓存目录(只能上传在缓存目录中的文件)const fileType = 'jpg'// 生成一个新的文件名const fileName = Date.now() + '.' + fileType// 通过缓存路径+文件名 拼接出完整的路径const copyFilePath = context.cacheDir + '/' + fileName// 将文件 拷贝到 临时目录const file = fs.openSync(item, fs.OpenMode.READ_ONLY)fs.copyFileSync(file.fd, copyFilePath)// 发送请求this.uploadImg(fileName,context1,settings,offCanvas)})})})

2.上传图片并处理数据

async uploadImg (fileName:string,context:CanvasRenderingContext2D,settings:RenderingContextSettings,offCanvas:OffscreenCanvas){let formData = new FormData()formData.append('file', `internal://cache/${fileName}`)const res:ESObject =  awaitaxios.post<string, AxiosResponse<string>, FormData>('你的url', formData, {headers: { 'Content-Type': 'multipart/form-data' ,'X-Auth-Token': token},context: getContext(this),// 上传进度// onUploadProgress: (progressEvent: AxiosProgressEvent): void => {//   console.info(fileName,progressEvent && progressEvent.loaded && progressEvent.total ? Math.ceil(progressEvent.loaded / progressEvent.total * 100) + '%' : '0%');// },})//   .then((res:AxiosResponse<string>)=>{//   console.log(JSON.stringify(res))// }).catch((err:Error)=>{//   console.log(JSON.stringify(err))// })const res2:type1 = res.datathis.imgSize = res.data.img_sizeconsole.log(JSON.stringify(res2))// 数据处理res2.boxes_xywh_Relative.forEach((item,index) => {const x = Number(item[0].toFixed(3)) * 3;const y = Number(item[1].toFixed(3)) * 3;const w = Number(item[2].toFixed(3)) * 3;const h = Number(item[3].toFixed(3)) * 3;res2.detection_scores.forEach((score,index1) => {if(index==index1){const formattedScore = Number(score.toFixed(3));res2.detection_classes.forEach((cls) => {// 绘制canvasthis.draw(x, y, w, h, context, settings, offCanvas, formattedScore, cls);});}});});}

3.进行绘制

// 绘制draw(x:number,y:number,w:number,h:number,context:CanvasRenderingContext2D,settings:RenderingContextSettings,offCanvas:OffscreenCanvas,item1?:number,item2?:string) {// context.clearRect(x*100, y*100, w*100, h*100); // 清理画布内容let offContext = offCanvas.getContext("2d", settings)// 框颜色offContext.strokeStyle ='#FF0000'//框宽offContext.lineWidth = 1context.fillStyle = '#FF0000'//字体大小offContext.font = '16vp sans-serif'// 绘制置信度offContext.fillText(item1?.toString(), x*100, (y-0.2)*100)// 绘制detection_classesoffContext.fillText(item2, x*100, y*100)// 绘制标注offContext.strokeRect(x*100, y*100, w*100, h*100)// offContext.strokeRect(40, 40, 200, 150)let image = offCanvas.transferToImageBitmap()context.transferFromImageBitmap(image)this.toDataURL = context.toDataURL("image/png", 0.92)}

4.布局代码

  Scroll(){Column(){ForEach(this.arr,(item:type2)=>{Stack(){Image(item.url).width('100%').height('50%').objectFit(ImageFit.Contain)Canvas(item.context)// .margin({left:20,top:20}).width('100%').height('50%').onReady(() => {})}})}}

 三.完整代码

import picker from '@ohos.file.picker';
import fs from '@ohos.file.fs';
import axios, { AxiosError, AxiosProgressEvent, AxiosResponse, FormData } from '@ohos/axios';
import { componentSnapshot, promptAction } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { image } from '@kit.ImageKit';interface type1{detection_classes:Array<string>boxes_xywh_Relative:Array<Array<number>>boxes_xywh_Absolute:Array<Array<number>>detection_scores:Array<number>img_size:Array<number>
}interface  type2{context:CanvasRenderingContext2Durl:string
}
const token = '你的token'
// 实例化 选项对象
const photoSelectOptions = new picker.PhotoSelectOptions();// 过滤选择媒体文件类型为IMAGE
photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
// 选择媒体文件的最大数目
photoSelectOptions.maxSelectNumber = 3;
@Entry
@Component
struct Page03_uploadImg {@State arr:Array<type2>=[]@State @Watch('draw')content: string = '';@State imgSize:Array<number>=[]@State toDataURL: string = ""// 图片上传  axiosasync uploadImg (fileName:string,context:CanvasRenderingContext2D,settings:RenderingContextSettings,offCanvas:OffscreenCanvas){let formData = new FormData()formData.append('file', `internal://cache/${fileName}`)const res:ESObject =  awaitaxios.post<string, AxiosResponse<string>, FormData>('你的url', formData, {headers: { 'Content-Type': 'multipart/form-data' ,'X-Auth-Token': token},context: getContext(this),// 上传进度// onUploadProgress: (progressEvent: AxiosProgressEvent): void => {//   console.info(fileName,progressEvent && progressEvent.loaded && progressEvent.total ? Math.ceil(progressEvent.loaded / progressEvent.total * 100) + '%' : '0%');// },})//   .then((res:AxiosResponse<string>)=>{//   console.log(JSON.stringify(res))// }).catch((err:Error)=>{//   console.log(JSON.stringify(err))// })const res2:type1 = res.datathis.imgSize = res.data.img_sizeconsole.log(JSON.stringify(res2))// 数据处理res2.boxes_xywh_Relative.forEach((item,index) => {const x = Number(item[0].toFixed(3)) * 3;const y = Number(item[1].toFixed(3)) * 3;const w = Number(item[2].toFixed(3)) * 3;const h = Number(item[3].toFixed(3)) * 3;res2.detection_scores.forEach((score,index1) => {if(index==index1){const formattedScore = Number(score.toFixed(3));res2.detection_classes.forEach((cls) => {// 绘制canvasthis.draw(x, y, w, h, context, settings, offCanvas, formattedScore, cls);});}});});}// 绘制draw(x:number,y:number,w:number,h:number,context:CanvasRenderingContext2D,settings:RenderingContextSettings,offCanvas:OffscreenCanvas,item1?:number,item2?:string) {// context.clearRect(x*100, y*100, w*100, h*100); // 清理画布内容let offContext = offCanvas.getContext("2d", settings)// 框颜色offContext.strokeStyle ='#FF0000'//框宽offContext.lineWidth = 1context.fillStyle = '#FF0000'//字体大小offContext.font = '16vp sans-serif'// 绘制置信度offContext.fillText(item1?.toString(), x*100, (y-0.2)*100)// 绘制detection_classesoffContext.fillText(item2, x*100, y*100)// 绘制标注offContext.strokeRect(x*100, y*100, w*100, h*100)// offContext.strokeRect(40, 40, 200, 150)let image = offCanvas.transferToImageBitmap()context.transferFromImageBitmap(image)this.toDataURL = context.toDataURL("image/png", 0.92)}build() {Column() {Scroll(){Column(){ForEach(this.arr,(item:type2)=>{Stack(){Image(item.url).width('100%').height('50%').objectFit(ImageFit.Contain)Canvas(item.context)// .margin({left:20,top:20}).width('100%').height('50%').onReady(() => {})}})}}Button('选择并上传图片')   .position({ x: 100, y: 685 }).onClick(async () => {// 创建 图片选择对象const photoViewPicker = new picker.PhotoViewPicker();// 调用 select 方法,传入选项对象photoViewPicker.select(photoSelectOptions).then(async res => {const context = getContext(this)res.photoUris.forEach((item)=>{// this.str= itemlet  settings: RenderingContextSettings = new RenderingContextSettings(true)let context1: CanvasRenderingContext2D = new CanvasRenderingContext2D(settings)let offCanvas: OffscreenCanvas = new OffscreenCanvas(600, 600)this.arr.push({ url:item,context:context1 })// 三、拷贝文件到缓存目录// 将文件保存到缓存目录(只能上传在缓存目录中的文件)const fileType = 'jpg'// 生成一个新的文件名const fileName = Date.now() + '.' + fileType// 通过缓存路径+文件名 拼接出完整的路径const copyFilePath = context.cacheDir + '/' + fileName// 将文件 拷贝到 临时目录const file = fs.openSync(item, fs.OpenMode.READ_ONLY)fs.copyFileSync(file.fd, copyFilePath)// 发送请求this.uploadImg(fileName,context1,settings,offCanvas)})})})}.padding(15).height('90%')}
}

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

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

相关文章

vue-element-admin解决三级目录的KeepAlive缓存问题(详情版)

vue-element-admin解决三级目录的KeepAlive缓存问题&#xff08;详情版&#xff09; 本文章将从问题出现的角度看看KeepAlive的缓存问题&#xff0c;然后提出两种解决方法。本文章比较详细&#xff0c;如果只是看怎么解决&#xff0c;代码怎么改&#xff0c;请前往配置版。 一…

零工市场小程序应该有什么功能?

数字经济现如今正飞速发展&#xff0c;零工市场小程序在连接雇主与自由职业者方面发挥着越来越重要的作用。一个高效的零工市场小程序不仅需要具备基础的信息发布与匹配功能&#xff0c;还应该涵盖交易管理、安全保障以及个性化服务等多个方面。 那么&#xff0c;零工市场小程…

Ubuntu22.04下安装LDAP

目录 1 简单说明2 安装配置2.1 安装1、安装前准备2、安装 OpenLADP3、配置OpenLDAP4、设置基本组5、添加新组5、添加 OpenLDAP 用户 2.2 安装 LDAP 帐户管理器1、安装2、配置 LDAP 帐户管理器 3 简单使用3.1 创建一个组3.2 创建一个用户 总结 1 简单说明 之前写过在Centos下的…

nginx和tomcat负载均衡,动静分离

文章目录 一&#xff0c;tomcat1.tomca用途2.tomcat重要目录 二&#xff0c;nginx1.Nginx应用2.nginx作用3.nginx的正向代理和反向代理3.1正向代理3.2反向代理(单级)3.3反向代理(多级) 4.nginx负载均衡4.1Nginx支持的常见的分流算法1. 轮询(Round Robin):2.最少连接数(LeastCon…

[MRCTF2020]Hello_ misc

解压得一个png图片和一个flag.rar 图片拖入010editor 选择带zip头的这段蓝色全部复制&#xff0c;file-new-new Hex File&#xff0c;黏贴到新文件&#xff0c;另存为为1.zip 要密码,线索中断&#xff08;当然try to restore it.png&#xff0c;隐藏了zip压缩包&#xff0c;可…

uniapp - plugins的组件配置使用

点击进入到uniapp中mp-weixin的配置中 点击进入小程序的plugin的配置 在项目中&#xff0c;我们可引用插件的使用&#xff0c;例如一些快递100&#xff0c;点餐插件的业务引入 添加插件 在使用插件前&#xff0c;首先要在小程序管理后台的“设置-第三方服务-插件管理”中添加…

java ssl使用自定义证书或忽略证书

1.证书错误 Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 2.生成客户端证书 openssl x509 -in <(openssl s_client -connect 192.168.11.19:8101 -prexit 2>/dev/null) -ou…

C HTML格式解析与生成

cmake报错替换 if(NOT MyHTML_BUILD_WITHOUT_THREADS OR NOT MyCORE_BUILD_WITHOUT_THREADS) set(CMAKE_THREAD_PREFER_PTHREAD 1) if (WIN32) set(CMAKE_USE_WIN32_THREADS_INIT ON) set(CMAKE_THREAD_PREFER_PTHREADS TRUE) set(THREADS_PR…

windows配置jmeter定时任务

场景&#xff1a; 需要让脚本在指定的执行 步骤&#xff1a; 准备jmeter脚本&#xff0c;保证在命令行中可以调用脚本且脚本运行正常&#xff1a;"C:\Apache\jmeter\bin\jmeter.bat" -n -t C:\tests\test_plan.jmx -l C:\tests\results.jtl -t : 指定执行jmeter脚…

异步交互技术Ajax-Axios

目录 一、同步交互和异步交互 二、Ajax 1.概述 2.如何实现ajax请求 三、异步传输数据乱码的问题 regist.html页面代码 服务端代码处理 四、Axios 1. Axios的基本使用 &#xff08;1&#xff09;引入Axios文件 &#xff08;2&#xff09;使用Axios发送请求&#xff0…

通过Python绘制不同数据类型适合的可视化图表

在数据可视化中&#xff0c;对于描述数值变量与数值变量之间的关系常见的有散点图和热力图&#xff0c;以及描述数值变量与分类变量之间的关系常见的有条形图&#xff0c;饼图和折线图&#xff0c;可以通过使用Python的matplotlib和seaborn库来绘制图表进行可视化表达&#xff…

【大数据】什么是数据中台?

随着企业规模不断扩大、业务多元化——中台服务架构的应运而生。“中台”早期是由美军的作战体系演化而来的&#xff0c;技术上说的“中台”主要是指学习这种高效、灵活和强大的指挥作战体系。阿里在今年发布“双中台ET”数字化转型方法论&#xff0c;“双中台”指的是数字中台…

ResNet网络学习

简介 Residual Network 简称 ResNet (残差网络) 下面是ResNet的网络结构&#xff1a; ResNet详细介绍 原理 传统方法的问题&#xff1a; 对于一个网络&#xff0c;如果简单地增加深度&#xff0c;就会导致 梯度消失 或 梯度爆炸&#xff0c;我们采取的解决方法是 正则化。…

卸载nomachine

网上的方法:提示找不到命令 我的方法: step1. 终端输入 sudo find / -name nxserver 2>/dev/null确认 NoMachine 的实际安装路径。你可以使用 find 命令在系统中查找 nxserver 脚本的位置。 找到路径后,你可以使用该路径来卸载 NoMachine。 如下图,紫色框中是我的路径…

ProtoBuf简要介绍与快速上手使用(C++版)

文章目录 一、 初识ProtoBuf1. 序列化和反序列化概念2. ProtoBuf是什么3. ProtoBuf的使用特点 二、 讲解说明三、 快速上手1. 创建 .proto 文件2. 编译 contacts.proto 文件&#xff0c;生成C文件3. 序列化与反序列化的使用4. 小结 ProtoBuf 使用流程 一、 初识ProtoBuf 1. 序…

Linux权限维持实战

目录 介绍步骤 介绍 攻击者在获取服务器权限后&#xff0c;会通过一些技巧来隐藏自己的踪迹和后门文件 查看/tmp目录下的flag文件 查看/root目录下具有特殊文件属性的文件 操作机中共有几个SUID文件 操作机中共有几个SGID文件 查看操作机中ssh公私钥免密登陆 查看strace后门 …

Web3链上聚合器声呐已全球上线,开启区块链数据洞察新时代

在全球区块链技术高速发展的浪潮中&#xff0c;在创新发展理念的驱动下&#xff0c;区块链领域的工具类应用备受资本青睐。 2024年8月20日&#xff0c;由生纳&#xff08;香港&#xff09;国际集团倾力打造的一款链上应用工具——“声呐链上聚合器”&#xff0c;即“声呐链上数…

24暑假算法刷题 | Day39 | 动态规划 VII | LeetCode 198. 打家劫舍,213. 打家劫舍 II,337. 打家劫舍 III

目录 198. 打家劫舍题目描述题解 213. 打家劫舍 II题目描述题解 337. 打家劫舍 III题目描述题解 打家劫舍的一天 &#x1f608; 198. 打家劫舍 点此跳转题目链接 题目描述 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷…

贪心+栈。。

前言&#xff1a;这个题目一开始我没想通的就是如果s当前的一个字符或者之后的一个字符和当前t的尾巴是一样的&#xff0c;那么优先选哪一个&#xff0c;其实这个就要优先选t的 class Solution { public:string robotWithString(string s) {string ans;int cnt[26]{}, min 0; …

<C++> 二叉搜索树

目录 二叉搜索树 1. 概念 2. 二叉搜索树操作 2.1 基础结构 2.2 非递归版 1. 查找 2. 插入 3. 删除 2.3 递归版 1. 查找 2. 插入 3. 删除 2.4 拷贝构造函数 2.5 赋值运算符重载 2.6 析构函数 2.7 完整代码 3. 二叉搜索树的应用 4. 二叉搜索树的性能 二叉搜索树 1. 概念 二叉搜索…