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