当前位置: 首页 > news >正文

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

一.实现思路

现在需求是:后端会返回在这张图片上的相对位置,然后前端这边需要在图片上进行标注,就是画个框框圈起来,返回的数据里包括当前框的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.mrgr.cn/news/7984.html

相关文章:

  • git add . 报错 warning: LF will be replaced by CRLF in ******.vue.
  • 【分布式】分布式Session共享
  • Vue小玩意儿:vue3+express.js实现大文件分片上传
  • Python-Poc编写(6)
  • 鸿蒙服务卡片,点击事件,传值
  • Django 后端架构开发:文件云存储,从本地存储到腾讯COS桶集成
  • JDK17 隐藏类 Hidden Classes 介绍
  • 关于武汉芯景科技有限公司的RS232通信接口芯片XJ3243EEUI开发指南(兼容MAX3243EEUI)
  • mac 虚拟机PD19运行E-prime实验遇到E-prime unable to set display mode:0*80004001问题解决
  • QT Mainwindow下指定控件的setMouseTracking(true)和mousemoveevent函数失效-问题解决
  • 通信协议学习:CAN总线协议
  • Resilience4J服务熔断隔离与限流
  • 音频导出后为什么效果变差了 FL Studio音频导出设置推荐
  • QUIC协议:网络通信的革命性突破
  • JavaScript 文件上传详解与实现
  • Linux学习记录(十三)----信号
  • 在rk设备上挂载windows上某个文件夹,通过SSH实时将打包的文件保存至windows上
  • 京准同步:北斗授时设备(北斗校时服务器)操作指南
  • MySQL 函数
  • 如何通过数据管理优化储能系统的运行效率?