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

一.实现思路

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

  • 10
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值