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

一.实现思路

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

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

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

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

实现效果:

二.代码

1.进行图片选择

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

TypeScript 复制代码
    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.上传图片并处理数据

TypeScript 复制代码
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.进行绘制

TypeScript 复制代码
// 绘制
  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.布局代码

TypeScript 复制代码
  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(() => {
                  })
              }
            })
          }
        }

三.完整代码

TypeScript 复制代码
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%')
  }
}
相关推荐
Robot2513 小时前
「华为」人形机器人赛道投资首秀!
大数据·人工智能·科技·microsoft·华为·机器人
鸿蒙布道师3 小时前
鸿蒙NEXT开发动画案例5
android·ios·华为·harmonyos·鸿蒙系统·arkui·huawei
小诸葛的博客10 小时前
华为ensp实现跨vlan通信
网络·华为·智能路由器
康康这名还挺多12 小时前
鸿蒙HarmonyOS list优化一: list 结合 lazyforeach用法
数据结构·list·harmonyos·lazyforeach
晚秋大魔王16 小时前
OpenHarmony 开源鸿蒙南向开发——linux下使用make交叉编译第三方库——nettle库
linux·开源·harmonyos
python算法(魔法师版)19 小时前
.NET 在鸿蒙系统上的适配现状
华为od·华为·华为云·.net·wpf·harmonyos
bestadc21 小时前
鸿蒙 UIAbility组件与UI的数据同步和窗口关闭
harmonyos
枫叶丹41 天前
【HarmonyOS Next之旅】DevEco Studio使用指南(二十二)
华为·harmonyos·deveco studio·harmonyos next
ax一号街阿楠1 天前
华为FAT AP配置 真机
网络·华为·智能路由器
吗喽对你问好1 天前
华为5.7机考第一题充电桩问题Java代码实现
java·华为·排序