HarmonyOS5 便捷生活类——Canvas绘制蒙层在横屏的时候如何才能撑满屏幕

加入下方官方优快云班级,得鸿蒙礼盒

一起拿鸿蒙礼盒,戳我戳我!!

本期活动时间:2025年8月1日-12月31日

如有问题欢迎私聊我呀


问题现象

OCR识别卡证场景,通常需要在相机预览页面通过绘制Canvas蒙层,蒙层中切割出卡证识别区。横屏情况下出现蒙层未撑满屏幕的情况,要如何解决?

@Builder
canvasVerticalView() {
Stack({ alignContent: Alignment.Top }) {
Stack() {
XComponent({
type: XComponentType.SURFACE,
controller: this.mXComponentController,
imageAIOptions: this.options
})
.onLoad(async () => {
this.mXComponentController.setXComponentSurfaceRect({
surfaceWidth: display.getDefaultDisplaySync().width,
surfaceHeight: display.getDefaultDisplaySync().height - 150
});
surfaceId = this.mXComponentController.getXComponentSurfaceId();
setTimeout(async () => {
CameraIdManager.cameraShooting(0, surfaceId, context)
}, 500);
})
.layoutWeight(1)
.width('100%')
.rotate({ angle: -90 })
Canvas(this.context)
.width('100%')
.layoutWeight(1)
.onReady(() => {
this.context.globalCompositeOperation = 'xor'
// 计算画布尺寸
const canvasWidth = px2vp(display.getDefaultDisplaySync().width);
const canvasHeight = px2vp(display.getDefaultDisplaySync().height - 150);
// 计算图片绘制的位置,使其居中显示
const imgWidth = 360;
const imgHeight = 230;
const x = (canvasWidth - imgWidth) / 2;
const y = (canvasHeight - imgHeight) / 2;

// 绘制背景矩形(半透明蒙层)
this.context.beginPath();
this.context.rect(0, 0, canvasWidth, canvasHeight);
this.context.closePath();
this.context.fillStyle = 'rgba(0, 0, 0, 0.5)';
this.context.fill();
CameraIdManager.cropRect = {
x: vp2px(x),
y: vp2px(y),
size: {
width: 960,
height: 540
}
}
// 保存当前画布状态
this.context.save();
// 图片内部区域
// 圆角半径
const radius = 18;
this.context.beginPath();
this.context.moveTo(x + radius, y);
this.context.lineTo(x + imgWidth - radius, y);
this.context.arc(x + imgWidth - radius, y + radius, radius, 1.5 * Math.PI, 2 * Math.PI);
this.context.lineTo(x + imgWidth, y + imgHeight - radius);
this.context.arc(x + imgWidth - radius, y + imgHeight - radius, radius, 0, 0.5 * Math.PI);
this.context.lineTo(x + radius, y + imgHeight);
this.context.arc(x + radius, y + imgHeight - radius, radius, 0.5 * Math.PI, Math.PI);
this.context.lineTo(x, y + radius);
this.context.arc(x + radius, y + radius, radius, Math.PI, 1.5 * Math.PI);
this.context.closePath();
// 将图片内部区域设置为裁剪区域
this.context.clip();
// 在裁剪区域内清除蒙层
this.context.clearRect(x, y, imgWidth, imgHeight);
// 恢复默认的裁剪区域
this.context.restore();
// 将ImageBitmap绘制到主画布上,指定图片的宽度和高度
this.context.drawImage(this.img, x, y, imgWidth, imgHeight);
// 使画布重绘
this.context.canvas.invalidate();
})
}
.width('100%')
.layoutWeight(1)
}
.width('100%')
.layoutWeight(1)
.rotate({
x: 0,
y: 0,
z: 1,
centerX: '50%',
centerY: '50%',
angle: 90
})
}

背景知识

问题定位

当前实现横屏蒙层效果方案如下:

  1. 使用Stack容器包裹Canvas。
  2. Canvas中按竖屏绘制蒙版、裁剪卡证识别区,并使用drawImage()绘制卡证相框。
  3. 使用rotate()将Stack容器旋转90度,得到横屏效果。

分析结论

分析问题代码实现逻辑,发现Canvas画布宽高是基于手机屏幕宽高进行设置,但横屏旋转时操作对象为Canvas的父容器Stack,导致Canvas宽度(屏幕宽度)变为高度导致无法占满屏幕。

修改建议

调整实现方案,不再旋转父容器Stack,而是在使用clip()裁剪卡证识别区和使用drawImage()绘制卡证相框时旋转Canvas。将坐标系原点移动至屏幕中央,并顺时针旋转90度。

// 将图片坐标系平移至画布中央
this.context.translate(x + imgWidth / 2, y + imgHeight / 2)
// 顺时针旋转画布90度
this.context.rotate(Math.PI / 2)

调整后的示例参考如下:

 
@Builder
canvasVerticalView() {
Stack({ alignContent: Alignment.Top }) {
Stack() {
XComponent({
type: XComponentType.SURFACE,
controller: this.mXComponentController,
imageAIOptions: this.options
})
.onLoad(async () => {
this.mXComponentController.setXComponentSurfaceRect({
surfaceWidth: display.getDefaultDisplaySync().width,
surfaceHeight: display.getDefaultDisplaySync().height - 150
});
surfaceId = this.mXComponentController.getXComponentSurfaceId();
setTimeout(async () => {
CameraIdManager.cameraShooting(0, surfaceId, context)
}, 500);
})
.layoutWeight(1)
.width('100%')
.rotate({ angle: -90 })
Canvas(this.context)
.width('100%')
.layoutWeight(1)
.onReady(() => {
this.context.globalCompositeOperation = 'xor'
// 计算画布尺寸
const canvasWidth = px2vp(display.getDefaultDisplaySync().width);
const canvasHeight = px2vp(display.getDefaultDisplaySync().height - 150);
// 计算图片绘制的位置,使其居中显示
const imgWidth = 360;
const imgHeight = 230;
const x = (canvasWidth - imgWidth) / 2;
const y = (canvasHeight - imgHeight) / 2;

// 绘制背景矩形(半透明蒙层)
this.context.beginPath();
this.context.rect(0, 0, canvasWidth, canvasHeight);
this.context.closePath();
this.context.fillStyle = 'rgba(0, 0, 0, 0.5)';
this.context.fill();
CameraIdManager.cropRect = {
x: vp2px(x),
y: vp2px(y),
size: {
width: 960,
height: 540
}
}
// 保存当前画布状态
this.context.save();
// 将图片坐标系平移至画布中央
this.context.translate(x + imgWidth / 2, y + imgHeight / 2)
// 顺时针旋转画布90度
this.context.rotate(Math.PI / 2)
// 绘制蒙层区域
const radius = 18;
this.context.beginPath();
this.context.moveTo(x + radius, y);
this.context.lineTo(x + imgWidth - radius, y);
this.context.arc(x + imgWidth - radius, y + radius, radius, 1.5 * Math.PI, 2 * Math.PI);
this.context.lineTo(x + imgWidth, y + imgHeight - radius);
this.context.arc(x + imgWidth - radius, y + imgHeight - radius, radius, 0, 0.5 * Math.PI);
this.context.lineTo(x + radius, y + imgHeight);
this.context.arc(x + radius, y + imgHeight - radius, radius, 0.5 * Math.PI, Math.PI);
this.context.lineTo(x, y + radius);
this.context.arc(x + radius, y + radius, radius, Math.PI, 1.5 * Math.PI);
this.context.closePath();
// 将图片内部区域设置为裁剪区域
this.context.clip();
// 在裁剪区域内清除蒙层
this.context.clearRect(x, y, imgWidth, imgHeight);
// 恢复默认的裁剪区域
this.context.restore();
// 将ImageBitmap绘制到主画布上,指定图片的宽度和高度
this.context.drawImage(this.img, x, y, imgWidth, imgHeight);
// 使画布重绘
this.context.canvas.invalidate();
})
}
.width('100%')
.layoutWeight(1)
}
.width('100%')
.layoutWeight(1)
.rotate({
x: 0,
y: 0,
z: 1,
centerX: '50%',
centerY: '50%',
angle: 90
})
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值