From e45db226f3cc4b83d8a1a89e02351157230a3343 Mon Sep 17 00:00:00 2001 From: WangLin305 Date: Tue, 14 Oct 2025 16:21:14 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E4=BA=8E=E5=85=83=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E6=B5=81=E5=A2=9E=E5=8A=A0=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E7=94=BB=E9=9D=A2=E4=BA=BA=E8=84=B8=E6=A3=80=E6=B5=8B=E6=A1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.en.md | 3 +- README.md | 3 +- camera/Index.ets | 1 + .../ets/cameramanagers/MetadataManager.ets | 83 +++++++++++++++++++ entry/src/main/ets/pages/Index.ets | 62 ++++++++++++-- entry/src/main/ets/utils/CommonUtil.ets | 33 ++++++++ .../main/ets/viewmodels/PreviewViewModel.ets | 5 ++ 7 files changed, 180 insertions(+), 10 deletions(-) create mode 100644 camera/src/main/ets/cameramanagers/MetadataManager.ets diff --git a/README.en.md b/README.en.md index 94a46c5..6c5bea6 100644 --- a/README.en.md +++ b/README.en.md @@ -2,7 +2,7 @@ ### Overview -Based on the Camera Kit, this sample implements a range of core camera functionalities such as basic preview, preview image adjustments (switching between the front and rear cameras, flash light, focus, zoom, etc.), advanced preview functionalities (grid line, level, timeout pause, etc.), dual-channel preview, photographing (such as motion photo and delayed shooting), and video recording. It serves as a comprehensive reference and practice guidance for developing a custom camera service. +Based on the Camera Kit, this sample implements a range of core camera functionalities such as basic preview, preview image adjustments (switching between the front and rear cameras, flash light, focus, zoom, etc.), advanced preview functionalities (grid line, level, timeout pause, face detection, etc.), dual-channel preview, photographing (such as motion photo and delayed shooting), and video recording. It serves as a comprehensive reference and practice guidance for developing a custom camera service. ### Preview @@ -29,6 +29,7 @@ How to use: │ │ └──cameraManagers │ │ ├──CamaraManager.ets // Camera session management class. │ │ ├──ImageReceiverManager.ets // ImageReceiver preview stream. +│ │ ├──MetadataManager.ets // Metadata output stream. │ │ ├──OutputManager.ets // Output stream management abstract. │ │ ├──PhotoManager.ets // Photo stream management class. │ │ ├──VideoManager.ets // Video stream management class. diff --git a/README.md b/README.md index 0e423c8..227744d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ### 介绍 -本示例基于Camera Kit相机服务,使用ArkTS API实现基础预览、预览画面调整(前后置镜头切换、闪光灯、对焦、调焦、设置曝光中心点等)、预览进阶功能(网格线、水平仪、超时暂停等)、双路预览(获取预览帧数据)、拍照(动图拍摄、延迟拍摄等)、录像等核心功能。为开发者提供自定义相机开发的完整参考与实践指导。 +本示例基于Camera Kit相机服务,使用ArkTS API实现基础预览、预览画面调整(前后置镜头切换、闪光灯、对焦、调焦、设置曝光中心点等)、预览进阶功能(网格线、水平仪、人脸检测、超时暂停等)、双路预览(获取预览帧数据)、拍照(动图拍摄、延迟拍摄等)、录像等核心功能。为开发者提供自定义相机开发的完整参考与实践指导。 ### 效果预览 @@ -28,6 +28,7 @@ │ │ └──cameraManagers │ │ ├──CamaraManager.ets // 相机会话管理类 │ │ ├──ImageReceiverManager.ets // ImageReceiver预览流管理类 +│ │ ├──MetadataManager.ets // 元数据输出流管理类 │ │ ├──OutputManager.ets // 输出流管理类抽象接口 │ │ ├──PhotoManager.ets // 拍照流管理类 │ │ ├──VideoManager.ets // 视频流管理类 diff --git a/camera/Index.ets b/camera/Index.ets index ccfbb99..c8eba2c 100644 --- a/camera/Index.ets +++ b/camera/Index.ets @@ -18,5 +18,6 @@ export { PreviewManager } from './src/main/ets/cameramanagers/PreviewManager'; export { PhotoManager } from './src/main/ets/cameramanagers/PhotoManager'; export { VideoManager, AVRecorderState } from './src/main/ets/cameramanagers/VideoManager'; export { ImageReceiverManager } from './src/main/ets/cameramanagers/ImageReceiverManager'; +export { MetadataManager } from './src/main/ets/cameramanagers/MetadataManager'; export { GridLine } from './src/main/ets/components/GridLine'; export { LevelIndicator } from './src/main/ets/components/LevelIndicator'; diff --git a/camera/src/main/ets/cameramanagers/MetadataManager.ets b/camera/src/main/ets/cameramanagers/MetadataManager.ets new file mode 100644 index 0000000..934cd04 --- /dev/null +++ b/camera/src/main/ets/cameramanagers/MetadataManager.ets @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 ("the License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import OutputManager, { CreateOutputConfig } from "./OutputManager"; +import { BusinessError } from '@kit.BasicServicesKit'; +import { camera } from "@kit.CameraKit"; +import { Logger } from "commons"; + +const TAG_LOG = 'MetadataManager'; + +export class MetadataManager implements OutputManager { + output?: camera.MetadataOutput; + isActive: boolean = true; + onMetadataObjectsAvailable: (faceBoxArr: camera.Rect[]) => void; + + constructor(onMetadataObjectsAvailable: (faceBoxArr: camera.Rect[]) => void) { + this.onMetadataObjectsAvailable = onMetadataObjectsAvailable; + } + + async createOutput(config: CreateOutputConfig) { + const cameraOutputCap = config.cameraManager?.getSupportedOutputCapability(config.device, config.sceneMode); + if (!cameraOutputCap) { + Logger.error(TAG_LOG, 'Failed to get supported output capability.'); + return; + } + let metadataObjectTypes: Array = cameraOutputCap!.supportedMetadataObjectTypes; + try { + this.output = config.cameraManager?.createMetadataOutput(metadataObjectTypes); + if (this.output) { + this.addOutputListener(this.output); + } + } catch (error) { + console.error(`Failed to createMetadataOutput, error code: ${error.code}`); + } + return this.output; + } + + addOutputListener(output: camera.MetadataOutput) { + this.addMetadataObjectsAvailableListener(output); + this.addMetadataErrorListener(output); + } + + addMetadataObjectsAvailableListener(metadataOutput: camera.MetadataOutput): void { + metadataOutput.on('metadataObjectsAvailable', + (err: BusinessError, metadataObjectArr: Array) => { + if (err !== undefined && err.code !== 0) { + return; + } + let boxRectArr: camera.Rect[] = []; + metadataObjectArr.forEach((obj: camera.MetadataObject)=>{ + boxRectArr.push(obj.boundingBox); + }); + this.onMetadataObjectsAvailable(boxRectArr); + }); + } + + addMetadataErrorListener(metadataOutput: camera.MetadataOutput): void { + metadataOutput.on('error', (metadataOutputError: BusinessError) => { + Logger.error(TAG_LOG, `Metadata output error code: ${metadataOutputError.code}`); + }); + } + + async release() { + try { + await this.output?.release(); + } catch (exception) { + Logger.error(TAG_LOG, `release failed, code is ${exception.code}, message is ${exception.message}`); + } + this.output = undefined; + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets index 8b3f6d2..4e13044 100644 --- a/entry/src/main/ets/pages/Index.ets +++ b/entry/src/main/ets/pages/Index.ets @@ -15,27 +15,35 @@ import { sensor } from '@kit.SensorServiceKit'; import { common } from '@kit.AbilityKit'; -import { display } from '@kit.ArkUI'; +import { display, window } from '@kit.ArkUI'; import { curves } from '@kit.ArkUI'; import { BusinessError } from '@kit.BasicServicesKit'; +import { camera } from '@kit.CameraKit'; import { CameraManager, GridLine, ImageReceiverManager, LevelIndicator, + MetadataManager, PhotoManager, PreviewManager, VideoManager } from 'camera'; import CameraConstant from '../constants/Constants'; -import { calCameraPoint, getClampedChildPosition, limitNumberInRange, showToast } from '../utils/CommonUtil'; +import { + calCameraPoint, + calFaceBoxLinePoint, + getClampedChildPosition, + limitNumberInRange, + showToast +} from '../utils/CommonUtil'; import RefreshableTimer from '../utils/RefreshableTimer'; import PermissionManager from '../utils/PermissionManager'; import ZoomButtonsView from '../views/ZoomButtonsView'; import ModeButtonsView from '../views/ModeButtonsView'; import SettingButtonsView from '../views/SettingButtonsView'; import OperateButtonsView from '../views/OperateButtonsView'; -import PreviewViewModel from '../viewmodels/PreviewViewModel'; +import PreviewViewModel, { LinePoint } from '../viewmodels/PreviewViewModel'; import { Logger } from 'commons'; @Extend(SymbolGlyph) @@ -47,14 +55,14 @@ function funcButtonStyle() { .backgroundColor('#664D4D4D') } -const TAG = 'Index' +const TAG = 'Index'; @Entry @Component struct Index { private context: Context = this.getUIContext().getHostContext()!; private applicationContext = this.context.getApplicationContext(); - private windowClass = (this.context as common.UIAbilityContext).windowStage.getMainWindowSync(); + private windowClass: window.Window | undefined = undefined; @State videoManager: VideoManager = new VideoManager(this.context); @State isSinglePhoto: boolean = false; @State isLivePhoto: boolean = false; @@ -65,8 +73,11 @@ struct Index { private imageReceiverManager: ImageReceiverManager = new ImageReceiverManager(px => { this.onImageReceiver(px); }); + private metadataManager: MetadataManager = new MetadataManager(faceBoxArr => { + this.onMetadataObjectsAvailable(faceBoxArr); + }); private cameraManager: CameraManager = new CameraManager(this.context, [this.previewManager, - this.photoManager, this.videoManager, this.imageReceiverManager]); + this.photoManager, this.videoManager, this.imageReceiverManager, this.metadataManager]); @State previewVM: PreviewViewModel = new PreviewViewModel(this.getUIContext()); @State isGridLineVisible: boolean = false; @State isLevelIndicatorVisible: boolean = false; @@ -98,12 +109,19 @@ struct Index { @State isShowBlack: boolean = false; @StorageLink('captureClick') @Watch('onCaptureClick') captureClickFlag: number = 0; @State flashBlackOpacity: number = 1; + @State faceBoundingBoxArr: camera.Rect[] = []; async aboutToAppear() { this.addGravityEventListener(); this.initSleepTimer(); this.registerApplicationStateChange(); this.addOrientationChangeEventListener(); + try { + this.windowClass = (this.context as common.UIAbilityContext).windowStage.getMainWindowSync(); + } catch (exception) { + Logger.error(TAG, `getMainWindowSync failed, code is ${exception.code}, message is ${exception.message}`); + } + try { display.on('foldStatusChange', () => { this.onFoldStatusChange() @@ -131,11 +149,11 @@ struct Index { // [End addGravityEventListener] addOrientationChangeEventListener() { - this.windowClass.on('windowSizeChange', this.setPreviewSize); + this.windowClass?.on('windowSizeChange', this.setPreviewSize); } removeOrientationChangeEventListener() { - this.windowClass.off('windowSizeChange', this.setPreviewSize); + this.windowClass?.off('windowSizeChange', this.setPreviewSize); } onImageReceiver(pixelMap: PixelMap) { @@ -200,6 +218,17 @@ struct Index { this.previewVM.closePreviewBlur(); } + onMetadataObjectsAvailable(faceBoxArr: camera.Rect[]) { + faceBoxArr.forEach((value) => { + // Convert normalized coordinates to actual coordinates. + value.topLeftX *= this.previewVM.getPreviewWidth(); + value.topLeftY *= this.previewVM.getPreviewHeight(); + value.width *= this.previewVM.getPreviewWidth(); + value.height *= this.previewVM.getPreviewHeight(); + }) + this.faceBoundingBoxArr = faceBoxArr; + } + initZooms() { const zoomRange = this.cameraManager.getZoomRange(); const minZoom = zoomRange[0]; @@ -246,6 +275,17 @@ struct Index { this.flashBlackAnim(); } + @Builder + faceBox(faceBoxRect: camera.Rect) { + ForEach(calFaceBoxLinePoint(faceBoxRect), (linePoint: LinePoint) => { + Line() + .startPoint([0, 0]) + .endPoint([linePoint.increment.x, linePoint.increment.y]) + .stroke(Color.White) + .position({ x: linePoint.start.x, y: linePoint.start.y }) + }) + } + @Builder preview() { // [Start Stack] @@ -337,6 +377,12 @@ struct Index { .position(this.getExposurePosition()) } + if (!this.isFocusBoxVisible) { + ForEach(this.faceBoundingBoxArr, (value: camera.Rect) => { + this.faceBox(value); + }) + } + if (this.isDelayTakePhoto) { Text(`${this.photoRemainder}S`) .fontSize(44) diff --git a/entry/src/main/ets/utils/CommonUtil.ets b/entry/src/main/ets/utils/CommonUtil.ets index dc1a899..c0c9729 100644 --- a/entry/src/main/ets/utils/CommonUtil.ets +++ b/entry/src/main/ets/utils/CommonUtil.ets @@ -18,6 +18,9 @@ import { camera } from '@kit.CameraKit'; import { display } from '@kit.ArkUI'; import { BusinessError } from '@kit.BasicServicesKit'; import { Logger } from 'commons'; +import { LinePoint } from '../viewmodels/PreviewViewModel'; + +const FACE_BOX_LINE_RATIO: number = 0.3; export function limitNumberInRange(src: number, range: number[]) { if (range.length < 2) { @@ -78,6 +81,36 @@ export function getClampedChildPosition(childSize: Size, parentSize: Size, point return { left, top }; } +// Calculate the coordinates of the face detection box. +export function calFaceBoxLinePoint(faceBoxRect: camera.Rect): LinePoint[] { + // The length of the sides of the box. + let lineLength: number = Math.min(faceBoxRect.width, faceBoxRect.height) * FACE_BOX_LINE_RATIO; + let linePoints: LinePoint[] = []; + + // The coordinates of the four vertices of the detection box. + let startPoints: camera.Point[] = [ + { x: faceBoxRect.topLeftX, y: faceBoxRect.topLeftY }, + { x: faceBoxRect.topLeftX + faceBoxRect.width, y: faceBoxRect.topLeftY }, + { x: faceBoxRect.topLeftX, y: faceBoxRect.topLeftY + faceBoxRect.height }, + { x: faceBoxRect.topLeftX + faceBoxRect.width, y: faceBoxRect.topLeftY + faceBoxRect.height }]; + + // Calculate the relative coordinates of each edge. + startPoints.forEach((startPoint: camera.Point) => { + let HorizontalLine: LinePoint = { + start: startPoint, + increment: { x: startPoint.x > faceBoxRect.topLeftX ? -lineLength : lineLength, y: 0 } + }; + + let verticalLine: LinePoint = { + start: startPoint, + increment: { x: 0, y: startPoint.y > faceBoxRect.topLeftY ? -lineLength : lineLength } + }; + + linePoints.push(HorizontalLine, verticalLine); + }); + return linePoints; +} + // [End getClampedChildPosition] export function showToast( diff --git a/entry/src/main/ets/viewmodels/PreviewViewModel.ets b/entry/src/main/ets/viewmodels/PreviewViewModel.ets index 87dcf0f..c5a4b7e 100644 --- a/entry/src/main/ets/viewmodels/PreviewViewModel.ets +++ b/entry/src/main/ets/viewmodels/PreviewViewModel.ets @@ -24,6 +24,11 @@ export enum CameraMode { VIDEO } +export interface LinePoint { + start: camera.Point, + increment: camera.Point +} + const TAG = 'PreviewViewModel'; /** -- Gitee