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

news2024/9/30 7:25:05

一.实现思路

现在需求是:后端会返回在这张图片上的相对位置,然后前端这边需要在图片上进行标注,就是画个框框圈起来,返回的数据里包括当前框的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= item
                  let  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 =  await
      axios.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.data
    this.imgSize = res.data.img_size
    console.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) => {
              // 绘制canvas
              this.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 = 1
    context.fillStyle = '#FF0000'
    //字体大小
    offContext.font = '16vp sans-serif'
    // 绘制置信度
    offContext.fillText(item1?.toString(), x*100, (y-0.2)*100)
    // 绘制detection_classes
    offContext.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:CanvasRenderingContext2D
  url: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 = ""
  // 图片上传  axios
  async uploadImg (fileName:string,context:CanvasRenderingContext2D,settings:RenderingContextSettings,offCanvas:OffscreenCanvas){
    let formData = new FormData()
    formData.append('file', `internal://cache/${fileName}`)
      const res:ESObject =  await
      axios.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.data
    this.imgSize = res.data.img_size
    console.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) => {
              // 绘制canvas
              this.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 = 1
    context.fillStyle = '#FF0000'
    //字体大小
    offContext.font = '16vp sans-serif'
    // 绘制置信度
    offContext.fillText(item1?.toString(), x*100, (y-0.2)*100)
    // 绘制detection_classes
    offContext.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= item
                  let  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.coloradmin.cn/o/2066295.html

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

相关文章

游戏app激励视频广告预加载位置,最大化广告收益

最近收到很多游戏类App开发者咨询激励视频广告&#xff0c;在帮助开发者分析产品的时候&#xff0c;特别是一些初级开发者的App产品&#xff0c;发现用户进入这些App&#xff0c;或者打开某个功能时就弹出激励视频广告&#xff0c;这样是违规的&#xff0c;并且用户看完广告也是…

golang每日一库——casbin开源的访问控制框架

文章目录 casbincasbin工作原理——PERM请求——Request策略——Policy匹配器——Matcher效果——Effect Model语法Request定义Policy定义Policy effect定义Matchers定义 编辑器例子1例子2例子3例子4例子5例子6例子7例子8例子9 casbin Casbin是一个强大且高效的开源访问控制库…

软件测试基础:功能测试知识详解

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、测试项目启动与研读需求文档 &#xff08;一&#xff09; 组建测试团队 1、测试团队中的角色 2、测试团队的基本责任 尽早地发现软件程序、系统或产品中…

postman使用指北

粘贴 cURL 请求 环境设置 作用&#xff1a;方便切换不同环境&#xff0c;比如配置本地环境/测试环境/线上环境&#xff0c;通过切换环境就可以请求对应环境的接口 配置环境 切换环境请求 Pre-request Script 可以在发送请求之前执行一些脚本操作 1. 常用指令 // 获取请求方…

C++中const的用法

const 我们都见过&#xff0c;但是今天&#xff0c;我们会从头开始重新再说const的所有用法。 一、const修饰普通变量 当我们定义一个变量时&#xff0c;前面加上const修饰的话&#xff0c;这个变量将不再能被修改&#xff0c;称之为常变量。例如&#xff1a; int a10; a20;…

ESD分类和等级划分

1、HBM&#xff1a;Human Body Model&#xff0c;人体模型 2、CDM&#xff1a;Charged Device Model&#xff0c;充电器件模型 3、MM&#xff1a;Machine Model&#xff0c;机器模型&#xff1a; 数据来源网站

总结Java文件操作

文件&#xff1a;文件是一个广义的概念 在操作系统中文件可以指硬件资源和软件资源为文件&#xff1b;也可以指存储在硬盘上的文件&#xff0c;文件夹也是文件&#xff1b;文件夹是通俗的叫法&#xff0c;专业的叫法是目录&#xff1b; 查看我们的硬盘&#xff0c;我们可以发…

C语言分析数据在内存中的存储一:(整形在内存中的存储)

数据类型介绍 我们知道C语言有很多内置类型&#xff1a; char //字符数据类型 1 个字节short //短整型 2 个字节int //整形 4 个字节long //长整形 4 个字节long long //更长的整形 8 个字节float //单精度浮点数 4 个字节dou…

Linux学习记录(十三)----信号

文章目录 6.信号1.信号的发送&#xff08;发送信号进程&#xff09;kill:raise:alarm : 2.信号的接收3.信号的处理信号父子进程间通信 7.信号灯(semaphore)创建信号灯函数控制信号灯函数PV操作 声明&#xff1a;本人撰写的为学习笔记内容来源于网络&#xff0c;如有侵权联系删除…

SQL Server中如何自动抓取阻塞

背景 当发数据库生阻塞时&#xff0c;可以通过SQL语句来获取当前阻塞的会话情况&#xff0c;可以得到下面的信息 说明&#xff1a;会话55阻塞了会话53。两个会话都执行了update test set fid10 where fid0。 但我们也经常碰到客户生产环境出现阻塞&#xff0c;由于不会抓取或者…

YOLOv8实现任意目录下命令行训练

问题 当你使用YOLOv8命令行训练模型的时候&#xff0c;如果当前执行的目录下没有相关的预训练模型文件&#xff0c;YOLOv8就会自动下载模型权重文件。这个是一个正常操作&#xff0c;但是你还会发现&#xff0c;当你在参数model中指定已有的&#xff0c;在其他目录下的预训练模…

实际案例:某日化集团主数据建设项目

一、建设背景1. 背景分析当前&#xff0c;该日化企业集团的主数据尚处于分散状态&#xff0c;分布于各业务系统中&#xff0c;缺乏一套专业的主数据管理系统进行统一管理。因此&#xff0c;数据无法在全集团范围内共享使用&#xff0c;且在业务端到端的流程拉通时&#xff0c;数…

WPS关闭后,进程依然在后台运行的解决办法

问题 wps启动后 在启动wps后&#xff0c;什么都不做&#xff0c;打开进程管理器&#xff0c;发现居然运行了3个wps进程&#xff1a; win10只会显示wps进程&#xff1a; win11显示比较准确&#xff1a; 关闭后 在关闭wps&#xff0c;再去任务管理器查看&#xff0c;发现在…

游戏开发设计模式之策略模式

目录 策略模式在游戏开发中的具体应用案例有哪些&#xff1f; 如何在Unity中实现策略模式以优化角色行为和AI策略&#xff1f; 策略模式与其他设计模式&#xff08;如观察者模式、状态模式&#xff09;在游戏开发中的比较优势是什么&#xff1f; 策略模式的优势 观察者模式…

基于SpringBoot的闲一品交易平台

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot框架 Java技术 工具&#xff1a;IDEA/Eclipse、Navicat、Maven 系统展示 首页 管理员…

【手撕OJ题】——160. 相交链表

目录 &#x1f552; 题目⌛ 方法① - 遍历记录链表长度⌛ 方法② - 双指针 &#x1f552; 题目 &#x1f50e; 160. 相交链表【难度&#xff1a;简单&#x1f7e2;】 &#x1f50e; 面试题 02.07. 链表相交 &#x1f50e; 剑指 Offer 52. 两个链表的第一个公共节点 给你两个单…

hadoop集成spark(spark on yarn)

文章目录 hadoop集成spark&#xff08;spark on yarn&#xff09;下载spark软件包spark文件设置spark-env.shworkers 环境变量设置发送spark到其余机器启动spark hadoop集成spark&#xff08;spark on yarn&#xff09; 在hadoop搭建完成的前提下&#xff0c;集成spark&#x…

【面试题系列Vue02】Vue Router 路由都有哪些模式?各模式之间有什么区别?

官方解析 Vue Router 路由有三种模式&#xff1a; hash 模式&#xff1a;使⽤ URL 中的 hash&#xff08;即 # 后面的内容&#xff09;来作为路由路径。 在这种模式下&#xff0c;页面不会重新加载&#xff0c;只会更新 hash 值&#xff0c;并触发路由变化&#xff0c;从而渲…

c语言杂谈系列:模拟虚函数

从整体来看&#xff0c;笔者的做法与之前的模拟多态十分相似&#xff0c;毕竟c多态的实现与虚函数密切相关 废话少说&#xff0c;see my code&#xff1a; kernel.c#include "kernel.h" #include <stdio.h>void shape_draw(struct shape_t* obj) {/* Call dr…

气膜粮仓:卓越的抗风雪能力与高性能材料—轻空间

在粮食储存领域&#xff0c;气膜粮仓以其卓越的抗风雪能力和高性能材料成为了现代农业的首选。其独特的设计和先进的材料使其在各种极端天气条件下依然能够保证粮食的安全和品质。 强抗风雪能力&#xff0c;保障粮仓安全 气膜粮仓采用了创新的结构设计&#xff0c;能够有效抵御…