加入下方官方优快云班级,得鸿蒙礼盒
本期活动时间: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
})
}
背景知识
- Canvas:使用CanvasRenderingContext2D实现矩形蒙层绘制,并剪切出卡证识别区域。
- 相机服务:相机拍照可参考拍照实践。
问题定位
当前实现横屏蒙层效果方案如下:
- 使用Stack容器包裹Canvas。
- Canvas中按竖屏绘制蒙版、裁剪卡证识别区,并使用drawImage()绘制卡证相框。
- 使用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
})
}
11万+

被折叠的 条评论
为什么被折叠?



