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

一.实现思路

现在需求是:后端会返回在这张图片上的相对位置,然后前端这边需要在图片上进行标注,就是画个框框圈起来,返回的数据里包括当前框的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%')
  }
}
相关推荐
张帅涛_6663 小时前
HarmonyOS开发之全局状态管理
华为·harmonyos
让开,我要吃人了3 小时前
HarmonyOS开发实战( Beta5.0)蓝牙实现服务端和客户端通讯详解
开发语言·前端·华为·移动开发·harmonyos·鸿蒙·鸿蒙系统
让开,我要吃人了4 小时前
HarmonyOS应用开发( Beta5.0)HOS-用户认证服务:面部识别
服务器·前端·华为·移动开发·嵌入式·harmonyos·鸿蒙
让开,我要吃人了7 小时前
HarmonyOS开发实战( Beta5.0)橡皮擦案例实践详解
开发语言·前端·华为·移动开发·harmonyos·鸿蒙·鸿蒙系统
ImomoTo7 小时前
HarmonyOS学习(十一)——安全管理
学习·安全·harmonyos·arkts·arkui
爱桥代码的程序媛16 小时前
HarmonyOS开发5.0【应用程序包】
分布式·harmonyos·鸿蒙·鸿蒙系统·openharmony·鸿蒙开发·程序包
爱桥代码的程序媛16 小时前
HarmonyOS开发5.0【rcp网络请求】
网络·移动开发·harmonyos·鸿蒙·鸿蒙系统·openharmony·rcp
让开,我要吃人了16 小时前
HarmonyOS应用开发( Beta5.0)一杯冰美式的时间“拿捏Grid组件”
服务器·前端·华为·移动开发·harmonyos·鸿蒙·openharmony
Android技术栈17 小时前
鸿蒙开发(API 12 Beta6版)【P2P模式】 网络WLAN服务开发
网络·harmonyos·鸿蒙·鸿蒙系统·p2p·openharmony·wlan
Android技术栈18 小时前
鸿蒙(API 12 Beta6版)图形加速【OpenGL ES平台内插模式】超帧功能开发
elasticsearch·harmonyos·鸿蒙·鸿蒙系统·openharmony·图形·超帧