diff --git a/README.md b/README.md
index 485c3fceece78e6b07779a5e27a7ee4f6760dfb7..ba81f595a04a90d011562c84fd1b9c5791b54c73 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@
* [NetworkManagement:网络管理与状态监听](NetworkManagement)
* [PreHttpRequestUseFiles:Image白块解决指导](PreHttpRequestUseFiles)
* [ImageEditTaskPool:基于TaskPool实现图片编辑功能](ImageEditTaskPool)
+* [SegmentedPhotograph:实现相机分段式拍照功能](SegmentedPhotograph)
## 使用说明
diff --git a/SegmentedPhotograph/AppScope/app.json5 b/SegmentedPhotograph/AppScope/app.json5
new file mode 100644
index 0000000000000000000000000000000000000000..5470c22e225cb61e721cda383f7074711b2b0911
--- /dev/null
+++ b/SegmentedPhotograph/AppScope/app.json5
@@ -0,0 +1,10 @@
+{
+ "app": {
+ "bundleName": "com.example.myapplication",
+ "vendor": "example",
+ "versionCode": 1000000,
+ "versionName": "1.0.0",
+ "icon": "$media:app_icon",
+ "label": "$string:app_name"
+ }
+}
diff --git a/SegmentedPhotograph/AppScope/resources/base/element/string.json b/SegmentedPhotograph/AppScope/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..50d02a419df7cd4ca197cac2ec2f781d2097ba23
--- /dev/null
+++ b/SegmentedPhotograph/AppScope/resources/base/element/string.json
@@ -0,0 +1,8 @@
+{
+ "string": [
+ {
+ "name": "app_name",
+ "value": "SegmentedPhotograph"
+ }
+ ]
+}
diff --git a/SegmentedPhotograph/AppScope/resources/base/media/app_icon.png b/SegmentedPhotograph/AppScope/resources/base/media/app_icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..a39445dc87828b76fed6d2ec470dd455c45319e3
Binary files /dev/null and b/SegmentedPhotograph/AppScope/resources/base/media/app_icon.png differ
diff --git a/SegmentedPhotograph/README.md b/SegmentedPhotograph/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..a94d33885ea874dfdaa5628de2a87949d144c624
--- /dev/null
+++ b/SegmentedPhotograph/README.md
@@ -0,0 +1,243 @@
+# 实现相机分段式拍照功能
+
+### 介绍
+
+分段式拍照是系统相机开发的重要功能之一,即相机拍照可输出低质量图,提升用户感知拍照速度,同时使用高质量图保证最后的成图质量达到系统相机的水平,既满足了后处理算法的需求,又不要阻塞前台的拍照速度,构筑相机性能竞争力,提升了用户的体验。
+
+### 使用说明
+
+1.点击单段式拍照按钮进入拍照页面,然后点击拍照,进入图片编辑页面,呈现高质量图。
+
+2.点击分段式拍照按钮进入拍照页面,然后点击拍照,进入图片编辑页面,优先显示低质量图,其次显示高质量图。
+
+### 效果图预览
+| 单段式拍照 | 分段式拍照 |
+|----------------------------------------|----------------------------------------|
+|
|
|
+
+
+### 工程目录
+```
+├──entry/src/main/ets
+│ ├──entryability
+│ │ └──EntryAbility.ets // Ability的生命周期回调内容
+│ ├──entrybackupability
+│ │ └──EntryBackupAbility.ets // 程序入口类
+│ ├──mode
+│ │ └──CameraService.ets // 模型层- 相机服务
+│ ├──pages
+│ │ ├──EditPage.ets // 视图层-编辑页面
+│ │ ├──IndexPage.ets // 视图层-首页
+│ │ └──PhotoPage.ets // 视图层-拍照页面
+│ └──views
+│ └──ModeComponent.ets // 视图层-拍照组件
+└──entry/src/main/resources // 应用静态资源目录
+```
+
+### 具体实现
+
+**单段式拍照:**
+
+单段式拍照使用了on(type:'photoAvailable',callback:AsyncCallback):void接口注册了全质量图的监听,默认不使能分段式拍照。具体操作步骤如下所示:
+
+1. 相机媒体数据写入[XComponent组件](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-basic-components-xcomponent-V5)中,用来显示图像效果。具体代码如下所示:
+
+ ```typescript
+ XComponent({
+ id: 'componentId',
+ type: 'surface',
+ controller: this.mXComponentController
+ })
+ .onLoad(async () => {
+ Logger.info(TAG, 'onLoad is called');
+ this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
+ GlobalContext.get().setObject('cameraDeviceIndex', this.defaultCameraDeviceIndex);
+ GlobalContext.get().setObject('xComponentSurfaceId', this.surfaceId);
+ let surfaceRect: SurfaceRect = {
+ surfaceWidth: Constants.X_COMPONENT_SURFACE_HEIGHT, surfaceHeight: Constants.X_COMPONENT_SURFACE_WIDTH
+ };
+ this.mXComponentController.setXComponentSurfaceRect(surfaceRect);
+ Logger.info(TAG, `onLoad surfaceId: ${this.surfaceId}`);
+ await CameraService.initCamera(this.surfaceId, this.defaultCameraDeviceIndex);
+ })
+ ```
+
+2. initCamera函数完成一个相机生命周期初始化的过程。
+- 首先通过[getCameraManager](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-camera-V5#cameragetcameramanager)来获取CameraMananger相机管理器类。
+- 调用[getSupportedCameras](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-camera-V5#getsupportedcameras)和[getSupportedOutputCapability](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-camera-V5#getsupportedoutputcapability11)方法来获取支持的camera设备以及设备能力集。
+- 调用[createPreviewOutput](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-camera-V5#createpreviewoutput)和[createPhotoOutput](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-camera-V5#createphotooutput11)方法来创建预览输出和拍照输出对象。
+- 使用CameraInput的open方法来打开相机输入,通过onCameraStatusChange函数来创建CameraManager注册回调。
+- 最后调用sessionFlowFn函数创建并开启Session。
+
+3. 确定拍照输出流。通过[CameraOutputCapability](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-camera-V5#cameraoutputcapability)类中的photoProfiles属性,可获取当前设备支持的拍照输出流,通过cameraManager.createPhotoOutput方法创建拍照输出流。
+
+4. 触发拍照。通过photoOutput类的[capture](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-camera-V5#capture-2)方法,执行拍照任务。
+
+ ```typescript
+ async takePicture(): Promise {
+ Logger.info(TAG, 'takePicture start');
+ let cameraDeviceIndex = GlobalContext.get().getT('cameraDeviceIndex');
+ let photoSettings: camera.PhotoCaptureSetting = {
+ quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,
+ mirror: cameraDeviceIndex ? true : false
+ };
+ await this.photoOutput?.capture(photoSettings);
+ Logger.info(TAG, 'takePicture end');
+ }
+ ```
+
+5. 设置拍照photoAvailable的回调来获取Photo对象,点击拍照按钮,触发此回调函数,调用getComponent方法根据图像的组件类型从图像中获取组件缓存ArrayBuffer,使用createImageSource方法来创建图片源实例,最后通过createPixelMap获取PixelMap对象。注意:如果已经注册了photoAssetAvailable回调,并且在Session开始之后又注册了photoAvailable回调,会导致流被重启。不建议开发者同时注册photoAvailable和photoAssetAvailable。
+
+ ```typescript
+ photoOutput.on('photoAvailable', (err: BusinessError, photo: camera.Photo) => {
+ Logger.info(TAG, 'photoAvailable begin');
+ if (err) {
+ Logger.info(TAG, `photoAvailable err:${err.code}`);
+ return;
+ }
+ let imageObj: image.Image = photo.main;
+ imageObj.getComponent(image.ComponentType.JPEG, (err: BusinessError, component: image.Component) => {
+ Logger.info(TAG, `getComponent start`);
+ if (err) {
+ Logger.info(TAG, `getComponent err:${err.code}`);
+ return;
+ }
+ let buffer: ArrayBuffer = component.byteBuffer;
+ let imageSource: image.ImageSource = image.createImageSource(buffer);
+ imageSource.createPixelMap((err: BusinessError, pixelMap: image.PixelMap) => {
+ if (err) {
+ Logger.error(TAG, `createPixelMap err:${err.code}`);
+ return;
+ }
+ this.handleImageInfo(pixelMap);
+ })
+ })
+ })
+ ```
+
+ 以上代码中执行handleImageInfo函数来对PixelMap进行全局存储并跳转到预览页面。具体代码如下所示:
+
+ ```typescript
+ handleSavePicture = (imageInfo: photoAccessHelper.PhotoAsset | image.PixelMap): void => {
+ Logger.info(TAG, 'handleSavePicture');
+ this.setImageInfo(imageInfo);
+ AppStorage.set('isOpenEditPage', true);
+ Logger.info(TAG, 'setImageInfo end');
+ }
+
+ setImageInfo(imageInfo: photoAccessHelper.PhotoAsset | image.PixelMap): void {
+ Logger.info(TAG, 'setImageInfo');
+ GlobalContext.get().setObject('imageInfo', imageInfo);
+ }
+ ```
+
+6. 进入到预览界面,通过GlobalContext.get().getT('imageInfo')方法获取PixelMap信息,并通过Image组件进行渲染显示。
+
+**分段式拍照:**
+
+分段式拍照是应用下发拍照任务后,系统将分多阶段上报不同质量的图片。在第一阶段,系统快速上报低质量图,应用通过on(type:'photoAssetAvailable',callback:AsyncCallback):void接口会收到一个PhotoAsset对象,通过该对象可调用媒体库接口,读取图片或落盘图片。在第二阶段,分段式子服务会根据系统压力以及定制化场景进行调度,将后处理好的原图回传给媒体库,替换低质量图。具体操作步骤如下所示:
+
+由于分段是拍照和单段式拍照步骤1-步骤4相同,就不再进行赘述。
+
+5. 设置拍照photoAssetAvailable的回调来获取photoAsset,点击拍照按钮,触发此回调函数,然后执行handlePhotoAssetCb函数来完成photoAsset全局的存储并跳转到预览页面。注意:如果已经注册了photoAssetAvailable回调,并且在Session开始之后又注册了photoAvailable回调,会导致流被重启。不建议开发者同时注册photoAvailable和photoAssetAvailable。
+
+ ```typescript
+ photoOutput.on('photoAssetAvailable', (err: BusinessError, photoAsset: photoAccessHelper.PhotoAsset) => {
+ Logger.info(TAG, 'photoAssetAvailable begin');
+ if (photoAsset === undefined) {
+ Logger.error(TAG, 'photoAsset is undefined');
+ return;
+ }
+ this.handlePhotoAssetCb(photoAsset);
+ });
+ ```
+
+ 以上代码中执行handleImageInfo函数来对photoAsset进行全局存储并跳转到预览页面。具体代码如下所示:
+
+ ```typescript
+ handleSavePicture = (imageInfo: photoAccessHelper.PhotoAsset | image.PixelMap): void => {
+ Logger.info(TAG, 'handleSavePicture');
+ this.setImageInfo(imageInfo);
+ AppStorage.set('isOpenEditPage', true);
+ Logger.info(TAG, 'setImageInfo end');
+ }
+
+ setImageInfo(imageInfo: photoAccessHelper.PhotoAsset | image.PixelMap): void {
+ Logger.info(TAG, 'setImageInfo');
+ GlobalContext.get().setObject('imageInfo', imageInfo);
+ }
+ ```
+
+6. 进入预览界面通过GlobalContext.get().getT('imageInfo')方法获取PhotoAsset信息,执行requestImage函数中的photoAccessHelper.MediaAssetManager.requestImageData方法根据不同的策略模式,请求图片资源数据,这里的请求策略为均衡模式BALANCE_MODE,
+ 最后分段式子服务会根据系统压力以及定制化场景进行调度,将后处理好的原图回传给媒体库来替换低质量图。具体代码如下所示:
+
+ ```typescript
+ photoBufferCallback: (arrayBuffer: ArrayBuffer) => void = (arrayBuffer: ArrayBuffer) => {
+ Logger.info(TAG, 'photoBufferCallback is called');
+ let imageSource = image.createImageSource(arrayBuffer);
+ imageSource.createPixelMap((err: BusinessError, data: image.PixelMap) => {
+ Logger.info(TAG, 'createPixelMap is called');
+ this.curPixelMap = data;
+ });
+ };
+
+ requestImage(requestImageParams: RequestImageParams): void {
+ try {
+ class MediaDataHandler implements photoAccessHelper.MediaAssetDataHandler {
+ onDataPrepared(data: ArrayBuffer, map: Map): void {
+ Logger.info(TAG, 'onDataPrepared begin');
+ Logger.info(TAG, `onDataPrepared quality: ${map['quality']}`);
+ requestImageParams.callback(data);
+ Logger.info(TAG, 'onDataPrepared end');
+ }
+ };
+ let requestOptions: photoAccessHelper.RequestOptions = {
+ deliveryMode: photoAccessHelper.DeliveryMode.BALANCE_MODE,
+ };
+ const handler = new MediaDataHandler();
+ photoAccessHelper.MediaAssetManager.requestImageData(requestImageParams.context, requestImageParams.photoAsset,
+ requestOptions, handler);
+ } catch (error) {
+ Logger.error(TAG, `Failed in requestImage, error code: ${error.code}`);
+ }
+ }
+
+ aboutToAppear() {
+ Logger.info(TAG, 'aboutToAppear begin');
+ if (this.photoMode === Constants.SUBSECTION_MODE) {
+ let curPhotoAsset = GlobalContext.get().getT('imageInfo');
+ this.photoUri = curPhotoAsset.uri;
+ let requestImageParams: RequestImageParams = {
+ context: getContext(),
+ photoAsset: curPhotoAsset,
+ callback: this.photoBufferCallback
+ };
+ this.requestImage(requestImageParams);
+ Logger.info(TAG, `aboutToAppear photoUri: ${this.photoUri}`);
+ } else if (this.photoMode === Constants.SINGLE_STAGE_MODE) {
+ this.curPixelMap = GlobalContext.get().getT('imageInfo');
+ }
+ }
+ ```
+
+7. 将步骤6获取的PixelMap对象数据通过Image组件进行渲染显示。
+
+
+### 相关权限
+
+[相机拍照权限](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/permissions-for-all-V5#ohospermissioncamera)
+
+
+### 依赖
+
+不涉及。
+
+### 约束与限制
+
+1.本示例仅支持标准系统上运行,支持设备:华为手机。
+
+2.HarmonyOS系统:HarmonyOS NEXT Release及以上。
+
+3.DevEco Studio版本:DevEco Studio NEXT Release及以上。
+
+4.HarmonyOS SDK版本:HarmonyOS NEXT Release SDK及以上。
\ No newline at end of file
diff --git a/SegmentedPhotograph/build-profile.json5 b/SegmentedPhotograph/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..1e69556b3411622cb2e87a87389653bb34f1b148
--- /dev/null
+++ b/SegmentedPhotograph/build-profile.json5
@@ -0,0 +1,41 @@
+{
+ "app": {
+ "signingConfigs": [],
+ "products": [
+ {
+ "name": "default",
+ "signingConfig": "default",
+ "compatibleSdkVersion": "5.0.0(12)",
+ "runtimeOS": "HarmonyOS",
+ "buildOption": {
+ "strictMode": {
+ "caseSensitiveCheck": true,
+ "useNormalizedOHMUrl": true
+ }
+ }
+ }
+ ],
+ "buildModeSet": [
+ {
+ "name": "debug",
+ },
+ {
+ "name": "release"
+ }
+ ]
+ },
+ "modules": [
+ {
+ "name": "entry",
+ "srcPath": "./entry",
+ "targets": [
+ {
+ "name": "default",
+ "applyToProducts": [
+ "default"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/SegmentedPhotograph/entry/build-profile.json5 b/SegmentedPhotograph/entry/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..4d611879c7913fb0610c686e2399258ab3a6dad1
--- /dev/null
+++ b/SegmentedPhotograph/entry/build-profile.json5
@@ -0,0 +1,28 @@
+{
+ "apiType": "stageMode",
+ "buildOption": {
+ },
+ "buildOptionSet": [
+ {
+ "name": "release",
+ "arkOptions": {
+ "obfuscation": {
+ "ruleOptions": {
+ "enable": false,
+ "files": [
+ "./obfuscation-rules.txt"
+ ]
+ }
+ }
+ }
+ },
+ ],
+ "targets": [
+ {
+ "name": "default"
+ },
+ {
+ "name": "ohosTest",
+ }
+ ]
+}
\ No newline at end of file
diff --git a/SegmentedPhotograph/entry/hvigorfile.ts b/SegmentedPhotograph/entry/hvigorfile.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c6edcd90486dd5a853cf7d34c8647f08414ca7a3
--- /dev/null
+++ b/SegmentedPhotograph/entry/hvigorfile.ts
@@ -0,0 +1,6 @@
+import { hapTasks } from '@ohos/hvigor-ohos-plugin';
+
+export default {
+ system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
+ plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
+}
diff --git a/SegmentedPhotograph/entry/oh-package.json5 b/SegmentedPhotograph/entry/oh-package.json5
new file mode 100644
index 0000000000000000000000000000000000000000..248c3b7541a589682a250f86a6d3ecf7414d2d6a
--- /dev/null
+++ b/SegmentedPhotograph/entry/oh-package.json5
@@ -0,0 +1,10 @@
+{
+ "name": "entry",
+ "version": "1.0.0",
+ "description": "Please describe the basic information.",
+ "main": "",
+ "author": "",
+ "license": "",
+ "dependencies": {}
+}
+
diff --git a/SegmentedPhotograph/entry/src/main/ets/common/Constants.ets b/SegmentedPhotograph/entry/src/main/ets/common/Constants.ets
new file mode 100644
index 0000000000000000000000000000000000000000..6e8a47ef6c223e3403fc9b912b9df9ab1923efbb
--- /dev/null
+++ b/SegmentedPhotograph/entry/src/main/ets/common/Constants.ets
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2024 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.
+ */
+
+export class Constants {
+ /**
+ * Surface width in xComponent.
+ */
+ static readonly X_COMPONENT_SURFACE_WIDTH = 1920;
+ /**
+ * Surface height in xComponent.
+ */
+ static readonly X_COMPONENT_SURFACE_HEIGHT = 1080;
+ /**
+ * Border width in xComponent.
+ */
+ static readonly X_COMPONENT_BORDER_WIDTH = 0.5;
+ /**
+ * Border width in capture button.
+ */
+ static readonly CAPTURE_BUTTON_BORDER_WIDTH = 3;
+ /**
+ * Border radius size in capture button.
+ */
+ static readonly CAPTURE_BUTTON_BORDER_RADIUS = 70;
+ /**
+ * Margins of the component.
+ */
+ static readonly CAPTURE_BUTTON_COLUMN_MARGIN = 24;
+ /**
+ * Margins of the component.
+ */
+ static readonly CAPTURE_TOP_COLUMN_MARGIN = 69;
+ /**
+ * Paddings of the component.
+ */
+ static readonly CAPTURE_BUTTON_COLUMN_PADDING = 24;
+ /**
+ * Size of the back icon.
+ */
+ static readonly BACK_ICON_SIZE = 24;
+ /**
+ * Size of the back icon.
+ */
+ static readonly IMAGE_SIZE = 25;
+ /**
+ * Margins of the back icon.
+ */
+ static readonly BACK_ICON_MARGIN = 24;
+ /**
+ * The full percentage of component.
+ */
+ static readonly FULL_PERCENT: string = '100%';
+ /**
+ * The Eighty-five percent of component.
+ */
+ static readonly EIGHTY_FIVE_PERCENT: string = '85%';
+ /**
+ * The Eighty percent of component.
+ */
+ static readonly EIGHTY_PERCENT: number = 516;
+ /**
+ * The seventy-five percent of the components.
+ */
+ static readonly SEVENTY_FIVE_PERCENT: string = '75%';
+ /**
+ * The seventy percent of the components.
+ */
+ static readonly SEVENTY_PERCENT: string = '70%';
+ /**
+ * The forty percent of the components.
+ */
+ static readonly FORTY_PERCENT: string = '40%';
+ /**
+ * The thirty percent of the components.
+ */
+ static readonly THIRTY_PERCENT: string = '30%';
+ /**
+ * The fifteen percent of the bottom of the margin.
+ */
+ static readonly FIFTEEN_PERCENT: string = '15%';
+ /**
+ * The ten percent of the bottom of the margin.
+ */
+ static readonly TEN_PERCENT: string = '10%';
+ /**
+ * The zero percent of the bottom of the margin.
+ */
+ static readonly ZERO_PERCENT: string = '0%';
+ /**
+ * border radius.
+ */
+ static readonly TEXT_BORDER_RADIUS: number = 25;
+ /**
+ * font size.
+ */
+ static readonly FONT_SIZE_14: number = 14;
+ /**
+ * column space.
+ */
+ static readonly COLUMN_SPACE_24: number = 24;
+ /**
+ * row space.
+ */
+ static readonly ROW_SPACE_24: number = 24;
+ /**
+ * default zoom ratio.
+ */
+ static readonly DEFAULT_ZOOM_RATIO: number = 1;
+ /**
+ * border radius.
+ */
+ static readonly BORDER_RADIUS_14: number = 14;
+ /**
+ * default zoom radio min.
+ */
+ static readonly ZOOM_RADIO_MIN: number = 1;
+ /**
+ * default zoom radio max.
+ */
+ static readonly ZOOM_RADIO_MAX: number = 6;
+ /**
+ * default zoom radio step.
+ */
+ static readonly ZOOM_RADIO_MAX_STEP: number = 0.1;
+ /**
+ * default zoom radio step.
+ */
+ static readonly ZOOM_RADIO_MIN_STEP: number = 0.01;
+ /**
+ * capture Column width.
+ */
+ static readonly CAPTURE_COLUMN_WIDTH: number = 50;
+ /**
+ * capture Column width.
+ */
+ static readonly CAPTURE_ROW_HEIGHT: number = 28;
+ /**
+ * AUDIO_BITRATE.
+ */
+ static readonly AUDIO_BITRATE: number = 48000;
+ /**
+ * AUDIO_CHANNELS.
+ */
+ static readonly AUDIO_CHANNELS: number = 2;
+ /**
+ * AUDIO_SAMPLE_RATE.
+ */
+ static readonly AUDIO_SAMPLE_RATE: number = 48000;
+ /**
+ * VIDEO_BITRATE.
+ */
+ static readonly VIDEO_BITRATE: number = 512000;
+ /**
+ * VIDEO_FRAME.
+ */
+ static readonly MAX_VIDEO_FRAME: number = 60;
+ /**
+ * FLASH_POSITION_X.
+ */
+ static readonly FLASH_POSITION_X: number = 0;
+ /**
+ * FLASH_POSITION_Y.
+ */
+ static readonly FLASH_POSITION_Y: number = 50;
+ /**
+ * SINGLE_STAGE_MODE
+ */
+ static readonly SINGLE_STAGE_MODE: string = 'singleStageMode';
+ /**
+ * SUBSECTION_MODE
+ */
+ static readonly SUBSECTION_MODE: string = 'subsectionMode';
+}
\ No newline at end of file
diff --git a/SegmentedPhotograph/entry/src/main/ets/common/utils/DateTimeUtil.ets b/SegmentedPhotograph/entry/src/main/ets/common/utils/DateTimeUtil.ets
new file mode 100644
index 0000000000000000000000000000000000000000..5a66ee434c3fd3704066931709ec5c13ffcd1900
--- /dev/null
+++ b/SegmentedPhotograph/entry/src/main/ets/common/utils/DateTimeUtil.ets
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2024 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.
+ */
+
+/**
+ * @file Date tool
+ */
+export default class DateTimeUtil {
+
+ /**
+ * Hour, minute, second
+ */
+ getTime(): string {
+ const DATETIME = new Date();
+ return this.concatTime(DATETIME.getHours(), DATETIME.getMinutes(), DATETIME.getSeconds());
+ }
+
+ /**
+ * Year Month Day
+ */
+ getDate(): string {
+ const DATETIME = new Date();
+ return this.concatDate(DATETIME.getFullYear(), DATETIME.getMonth() + 1, DATETIME.getDate());
+ }
+
+ /**
+ * Add 0 if the date is less than two digits
+ * @param value-Data value
+ */
+ fill(value: number): string {
+ let maxNumber = 9;
+ return (value > maxNumber ? '' : '0') + value;
+ }
+ /**
+ * Recording Time Timer
+ * @param millisecond-Data value
+ */
+ getVideoTime(millisecond: number): string {
+ let millisecond2minute = 60000;
+ let millisecond2second = 1000;
+ let minute = Math.floor(millisecond / millisecond2minute);
+ let second = Math.floor((millisecond - minute * millisecond2minute) / millisecond2second);
+ return `${this.fill(minute)} : ${this.fill(second)}`;
+ }
+ /**
+ * Modify the format of the year, month, and day
+ * @param year
+ * @param month
+ * @param date
+ */
+ concatDate(year: number, month: number, date: number): string {
+ return `${year}${this.fill(month)}${this.fill(date)}`;
+ }
+
+ /**
+ * Hour, minute, second format modifier
+ * @param hours
+ * @param minutes
+ * @param seconds
+ */
+ concatTime(hours: number, minutes: number, seconds: number): string {
+ return `${this.fill(hours)}${this.fill(minutes)}${this.fill(seconds)}`;
+ }
+}
diff --git a/SegmentedPhotograph/entry/src/main/ets/common/utils/GlobalContext.ets b/SegmentedPhotograph/entry/src/main/ets/common/utils/GlobalContext.ets
new file mode 100644
index 0000000000000000000000000000000000000000..cb44bb3baf5da4e55a88986a65b17ed0faf5d23b
--- /dev/null
+++ b/SegmentedPhotograph/entry/src/main/ets/common/utils/GlobalContext.ets
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2024 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 { common } from '@kit.AbilityKit';
+
+export class GlobalContext {
+ private static instance: GlobalContext;
+ private objects = new Map();
+ private cameraSettingContext: common.UIAbilityContext | undefined = undefined;
+ public static get(): GlobalContext {
+ if (!Boolean(GlobalContext.instance).valueOf()) {
+ GlobalContext.instance = new GlobalContext();
+ }
+ return GlobalContext.instance;
+ }
+
+ getT(value: string): T {
+ return this.objects.get(value) as T;
+ }
+
+ setObject(key: string, objectClass: Object): void {
+ this.objects.set(key, objectClass);
+ }
+
+ apply(value: string): void {
+ const func = this.objects.get(value);
+ if (func) {
+ (func as Function)();
+ }
+ }
+
+ public setCameraSettingContext(context: common.UIAbilityContext): void {
+ this.cameraSettingContext = context;
+ }
+
+ public getCameraSettingContext(): common.UIAbilityContext | undefined {
+ return this.cameraSettingContext;
+ }
+}
\ No newline at end of file
diff --git a/SegmentedPhotograph/entry/src/main/ets/common/utils/Logger.ets b/SegmentedPhotograph/entry/src/main/ets/common/utils/Logger.ets
new file mode 100644
index 0000000000000000000000000000000000000000..f7415c7df8564455b4587c5cbe41f44d4dfcc1cc
--- /dev/null
+++ b/SegmentedPhotograph/entry/src/main/ets/common/utils/Logger.ets
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2024 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 { hilog } from '@kit.PerformanceAnalysisKit';
+
+const TAG = 'SegmentedPhotograph';
+
+class Logger {
+ private domain: number;
+ private prefix: string;
+ private format: string = '%{public}s, %{public}s';
+
+ constructor(prefix: string) {
+ this.prefix = prefix;
+ this.domain = 0xFF00;
+ }
+
+ debug(...args: string[]): void {
+ hilog.debug(this.domain, this.prefix, this.format, args);
+ }
+
+ info(...args: string[]): void {
+ hilog.info(this.domain, this.prefix, this.format, args);
+ }
+
+ warn(...args: string[]): void {
+ hilog.warn(this.domain, this.prefix, this.format, args);
+ }
+
+ error(...args: string[]): void {
+ hilog.error(this.domain, this.prefix, this.format, args);
+ }
+}
+
+export default new Logger(TAG);
\ No newline at end of file
diff --git a/SegmentedPhotograph/entry/src/main/ets/entryability/EntryAbility.ets b/SegmentedPhotograph/entry/src/main/ets/entryability/EntryAbility.ets
new file mode 100644
index 0000000000000000000000000000000000000000..02fd7f22298079d5d2ebb8610088c5b5e7db16b9
--- /dev/null
+++ b/SegmentedPhotograph/entry/src/main/ets/entryability/EntryAbility.ets
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2024 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 { abilityAccessCtrl, UIAbility } from '@kit.AbilityKit';
+import { window } from '@kit.ArkUI';
+import { BusinessError } from '@kit.BasicServicesKit';
+import Logger from '../common/utils/Logger';
+import { GlobalContext } from '../common/utils/GlobalContext';
+
+const TAG = 'EntryAbility';
+
+export default class EntryAbility extends UIAbility {
+ onCreate(): void {
+ Logger.info(TAG, 'Ability onCreate');
+ GlobalContext.get().setCameraSettingContext(this.context);
+ }
+
+ onDestroy(): void {
+ Logger.info(TAG, 'Ability onDestroy');
+ }
+
+ onWindowStageCreate(windowStage: window.WindowStage): void {
+ Logger.info(TAG, 'Ability onWindowStageCreate');
+ windowStage.getMainWindow().then((win: window.Window): void => {
+ if (!win) {
+ return;
+ }
+ win.setWindowLayoutFullScreen(true);
+ });
+ this.requestPermissionsFn();
+ windowStage.loadContent('pages/IndexPage', (err, data) => {
+ if (err.code) {
+ Logger.error(TAG, 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
+ return;
+ }
+ Logger.info(TAG, 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
+ });
+ }
+
+ onWindowStageDestroy(): void {
+ Logger.info(TAG, 'Ability onWindowStageDestroy');
+ }
+
+ onForeground(): void {
+ Logger.info(TAG, 'Ability onForeground');
+ }
+
+ onBackground(): void {
+ Logger.info(TAG, 'Ability onBackground');
+ }
+
+ /**
+ * Get permission
+ */
+ requestPermissionsFn(): void {
+ let atManager = abilityAccessCtrl.createAtManager();
+ atManager.requestPermissionsFromUser(this.context, [
+ 'ohos.permission.CAMERA'
+ ]).then((): void => {
+ AppStorage.setOrCreate('isShow', true);
+ Logger.info(TAG, 'request Permissions success!');
+ }).catch((error: BusinessError): void => {
+ Logger.info(TAG, `requestPermissionsFromUser call Failed! error: ${error.code}`);
+ });
+ }
+}
\ No newline at end of file
diff --git a/SegmentedPhotograph/entry/src/main/ets/mode/CameraService.ets b/SegmentedPhotograph/entry/src/main/ets/mode/CameraService.ets
new file mode 100644
index 0000000000000000000000000000000000000000..2f0fa45dae40923e0fa9c51de9905fc8eff649aa
--- /dev/null
+++ b/SegmentedPhotograph/entry/src/main/ets/mode/CameraService.ets
@@ -0,0 +1,647 @@
+/*
+ * Copyright (c) 2024 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 { photoAccessHelper } from '@kit.MediaLibraryKit';
+import { camera } from '@kit.CameraKit';
+import { BusinessError } from '@kit.BasicServicesKit';
+import { JSON } from '@kit.ArkTS';
+import { GlobalContext } from '../common/utils/GlobalContext';
+import Logger from '../common/utils/Logger';
+import { Constants } from '../common/Constants';
+import { image } from '@kit.ImageKit';
+
+const TAG: string = 'CameraService';
+
+export class SliderValue {
+ min: number = 1;
+ max: number = 6;
+ step: number = 0.1;
+}
+
+class CameraService {
+ private cameraManager: camera.CameraManager | undefined = undefined;
+ private cameras: Array | Array = [];
+ private cameraInput: camera.CameraInput | undefined = undefined;
+ private previewOutput: camera.PreviewOutput | undefined = undefined;
+ private photoOutput: camera.PhotoOutput | undefined = undefined;
+ private photoMode: string | undefined = undefined;
+ private session: camera.PhotoSession | camera.VideoSession | undefined = undefined;
+ private handlePhotoAssetCb: (photoAsset: photoAccessHelper.PhotoAsset | image.PixelMap) => void = () => {
+ };
+ private curCameraDevice: camera.CameraDevice | undefined = undefined;
+ // One of the recommended photo resolution
+ private photoProfileObj: camera.Profile = {
+ format: 2000,
+ size: {
+ width: 1920,
+ height: 1080
+ }
+ };
+ // One of the recommended photo resolution
+ private previewProfileObj: camera.Profile = {
+ format: 1003,
+ size: {
+ width: 1920,
+ height: 1080
+ }
+ };
+ private curSceneMode: camera.SceneMode = camera.SceneMode.NORMAL_PHOTO;
+
+ constructor() {
+ }
+
+ setSavePictureCallback(callback: (photoAsset: photoAccessHelper.PhotoAsset | image.PixelMap) => void): void {
+ this.handlePhotoAssetCb = callback;
+ }
+
+ setSceneMode(sceneMode: camera.SceneMode): void {
+ this.curSceneMode = sceneMode;
+ }
+
+ getSceneMode(): camera.SceneMode {
+ return this.curSceneMode;
+ }
+
+ getPreviewProfile(cameraOutputCapability: camera.CameraOutputCapability): camera.Profile | undefined {
+ let previewProfiles = cameraOutputCapability.previewProfiles;
+ if (previewProfiles.length < 1) {
+ return undefined;
+ }
+ let index = previewProfiles.findIndex((previewProfile: camera.Profile) => {
+ return previewProfile.size.width === this.previewProfileObj.size.width &&
+ previewProfile.size.height === this.previewProfileObj.size.height &&
+ previewProfile.format === this.previewProfileObj.format;
+ });
+ if (index === -1) {
+ return undefined;
+ }
+ return previewProfiles[index];
+ }
+
+ getPhotoProfile(cameraOutputCapability: camera.CameraOutputCapability): camera.Profile | undefined {
+ let photoProfiles = cameraOutputCapability.photoProfiles;
+ if (photoProfiles.length < 1) {
+ return undefined;
+ }
+ let index = photoProfiles.findIndex((photoProfile: camera.Profile) => {
+ return photoProfile.size.width === this.photoProfileObj.size.width &&
+ photoProfile.size.height === this.photoProfileObj.size.height &&
+ photoProfile.format === this.photoProfileObj.format;
+ });
+ if (index === -1) {
+ return undefined;
+ }
+ return photoProfiles[index];
+ }
+
+ isSupportedSceneMode(cameraManager: camera.CameraManager, cameraDevice: camera.CameraDevice): boolean {
+ let sceneModes = cameraManager.getSupportedSceneModes(cameraDevice);
+ if (sceneModes === undefined) {
+ return false;
+ }
+ let index = sceneModes.findIndex((sceneMode: camera.SceneMode) => {
+ return sceneMode === this.curSceneMode;
+ });
+ if (index === -1) {
+ return false;
+ }
+ return true;
+ }
+
+ // DocsCode 1
+ /**
+ * Initialize Camera Functions
+ * @param surfaceId - Surface ID
+ * @param cameraDeviceIndex - Camera Device Index
+ * @returns No return value
+ */
+ async initCamera(surfaceId: string, cameraDeviceIndex: number): Promise {
+ Logger.debug(TAG, `initCamera cameraDeviceIndex: ${cameraDeviceIndex}`);
+ this.photoMode = AppStorage.get('photoMode');
+ if (!this.photoMode) {
+ return;
+ }
+ try {
+ await this.releaseCamera();
+ // Get Camera Manager Instance
+ this.cameraManager = this.getCameraManagerFn();
+ if (this.cameraManager === undefined) {
+ Logger.error(TAG, 'cameraManager is undefined');
+ return;
+ }
+ // Gets the camera device object that supports the specified
+ this.cameras = this.getSupportedCamerasFn(this.cameraManager);
+ if (this.cameras.length < 1 || this.cameras.length < cameraDeviceIndex + 1) {
+ return;
+ }
+ this.curCameraDevice = this.cameras[cameraDeviceIndex];
+ let isSupported = this.isSupportedSceneMode(this.cameraManager, this.curCameraDevice);
+ if (!isSupported) {
+ Logger.error(TAG, 'The current scene mode is not supported.');
+ return;
+ }
+ let cameraOutputCapability =
+ this.cameraManager.getSupportedOutputCapability(this.curCameraDevice, this.curSceneMode);
+ let previewProfile = this.getPreviewProfile(cameraOutputCapability);
+ if (previewProfile === undefined) {
+ Logger.error(TAG, 'The resolution of the current preview stream is not supported.');
+ return;
+ }
+ this.previewProfileObj = previewProfile;
+ // Creates the previewOutput output object
+ this.previewOutput = this.createPreviewOutputFn(this.cameraManager, this.previewProfileObj, surfaceId);
+ if (this.previewOutput === undefined) {
+ Logger.error(TAG, 'Failed to create the preview stream.');
+ return;
+ }
+ // Listening for preview events
+ this.previewOutputCallBack(this.previewOutput);
+ let photoProfile = this.getPhotoProfile(cameraOutputCapability);
+ if (photoProfile === undefined) {
+ Logger.error(TAG, 'The resolution of the current photo stream is not supported.');
+ return;
+ }
+ this.photoProfileObj = photoProfile;
+ // Creates a photoOutPut output object
+ this.photoOutput = this.createPhotoOutputFn(this.cameraManager, this.photoProfileObj);
+ if (this.photoOutput === undefined) {
+ Logger.error(TAG, 'Failed to create the photo stream.');
+ return;
+ }
+ // Creates a cameraInput output object
+ this.cameraInput = this.createCameraInputFn(this.cameraManager, this.curCameraDevice);
+ if (this.cameraInput === undefined) {
+ Logger.error(TAG, 'Failed to create the camera input.');
+ return;
+ }
+ // Turn on the camera
+ let isOpenSuccess = await this.cameraInputOpenFn(this.cameraInput);
+ if (!isOpenSuccess) {
+ Logger.error(TAG, 'Failed to open the camera.');
+ return;
+ }
+ // Camera status callback
+ this.onCameraStatusChange(this.cameraManager);
+ // Listens to CameraInput error events
+ this.onCameraInputChange(this.cameraInput, this.curCameraDevice);
+ // Session Process
+ await this.sessionFlowFn(this.cameraManager, this.cameraInput, this.previewOutput, this.photoOutput);
+ } catch (error) {
+ let err = error as BusinessError;
+ Logger.error(TAG, `initCamera fail: ${JSON.stringify(err)}`);
+ }
+ }
+
+ // DocsCode 1
+ /**
+ * Obtains the variable focal length range
+ */
+ getZoomRatioRange(): Array {
+ let zoomRatioRange: Array = [];
+ if (this.session !== undefined) {
+ zoomRatioRange = this.session.getZoomRatioRange();
+ }
+ return zoomRatioRange;
+ }
+
+ /**
+ * Zoom
+ */
+ setZoomRatioFn(zoomRatio: number): void {
+ Logger.info(TAG, `setZoomRatioFn value ${zoomRatio}`);
+ // Get the supported zoom range
+ try {
+ let zoomRatioRange = this.getZoomRatioRange();
+ Logger.info(TAG, `getZoomRatioRange success: ${JSON.stringify(zoomRatioRange)}`);
+ } catch (error) {
+ let err = error as BusinessError;
+ Logger.error(TAG, `getZoomRatioRange fail: ${JSON.stringify(err)}`);
+ }
+
+ try {
+ this.session?.setZoomRatio(zoomRatio);
+ Logger.info(TAG, 'setZoomRatioFn success');
+ } catch (error) {
+ let err = error as BusinessError;
+ Logger.error(TAG, `setZoomRatioFn fail: ${JSON.stringify(err)}`);
+ }
+ }
+
+ // DocsCode 3
+ /**
+ * Trigger a photo taking based on the specified parameters
+ */
+ async takePicture(): Promise {
+ Logger.info(TAG, 'takePicture start');
+ let cameraDeviceIndex = GlobalContext.get().getT('cameraDeviceIndex');
+ let photoSettings: camera.PhotoCaptureSetting = {
+ quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,
+ mirror: cameraDeviceIndex ? true : false
+ };
+ await this.photoOutput?.capture(photoSettings);
+ Logger.info(TAG, 'takePicture end');
+ }
+
+ // DocsCode 3
+ /**
+ * Release the session and related parameters
+ */
+ async releaseCamera(): Promise {
+ Logger.info(TAG, 'releaseCamera is called');
+ try {
+ await this.previewOutput?.release();
+ } catch (error) {
+ let err = error as BusinessError;
+ Logger.error(TAG, `previewOutput release fail: error: ${JSON.stringify(err)}`);
+ } finally {
+ this.previewOutput = undefined;
+ }
+ try {
+ await this.photoOutput?.release();
+ } catch (error) {
+ let err = error as BusinessError;
+ Logger.error(TAG, `photoOutput release fail: error: ${JSON.stringify(err)}`);
+ } finally {
+ this.photoOutput = undefined;
+ }
+ try {
+ await this.session?.release();
+ } catch (error) {
+ let err = error as BusinessError;
+ Logger.error(TAG, `captureSession release fail: error: ${JSON.stringify(err)}`);
+ } finally {
+ this.session = undefined;
+ }
+ try {
+ await this.cameraInput?.close();
+ } catch (error) {
+ let err = error as BusinessError;
+ Logger.error(TAG, `cameraInput close fail: error: ${JSON.stringify(err)}`);
+ } finally {
+ this.cameraInput = undefined;
+ }
+ this.offCameraStatusChange();
+ Logger.info(TAG, 'releaseCamera success');
+ }
+
+ /**
+ * Get Camera Manager Instance
+ */
+ getCameraManagerFn(): camera.CameraManager | undefined {
+ if (this.cameraManager) {
+ return this.cameraManager;
+ }
+ let cameraManager: camera.CameraManager;
+ try {
+ cameraManager = camera.getCameraManager(GlobalContext.get().getCameraSettingContext());
+ Logger.info(TAG, `getCameraManager success: ${cameraManager}`);
+ return cameraManager;
+ } catch (error) {
+ let err = error as BusinessError;
+ Logger.error(TAG, `getCameraManager failed: ${JSON.stringify(err)}`);
+ return undefined;
+ }
+ }
+
+ /**
+ * Gets the camera device object that supports the specified
+ */
+ getSupportedCamerasFn(cameraManager: camera.CameraManager): Array {
+ let supportedCameras: Array = [];
+ try {
+ supportedCameras = cameraManager.getSupportedCameras();
+ Logger.info(TAG, `getSupportedCameras success: ${this.cameras}, length: ${this.cameras?.length}`);
+ } catch (error) {
+ let err = error as BusinessError;
+ Logger.error(TAG, `getSupportedCameras failed: ${JSON.stringify(err)}`);
+ }
+ return supportedCameras;
+ }
+
+ /**
+ * Creates the previewOutput output object
+ */
+ createPreviewOutputFn(cameraManager: camera.CameraManager, previewProfileObj: camera.Profile,
+ surfaceId: string): camera.PreviewOutput | undefined {
+ let previewOutput: camera.PreviewOutput;
+ try {
+ previewOutput = cameraManager.createPreviewOutput(previewProfileObj, surfaceId);
+ Logger.info(TAG, `createPreviewOutput success: ${previewOutput}`);
+ return previewOutput;
+ } catch (error) {
+ let err = error as BusinessError;
+ Logger.error(TAG, `createPreviewOutput failed: ${JSON.stringify(err)}`);
+ return undefined;
+ }
+ }
+
+ // DocsCode 2
+ /**
+ * Creates a photoOutPut output object
+ */
+ createPhotoOutputFn(cameraManager: camera.CameraManager,
+ photoProfileObj: camera.Profile): camera.PhotoOutput | undefined {
+ let photoOutput: camera.PhotoOutput;
+ try {
+ photoOutput = cameraManager.createPhotoOutput(photoProfileObj);
+ Logger.info(TAG, `createPhotoOutputFn success: ${photoOutput}`);
+ return photoOutput;
+ } catch (error) {
+ let err = error as BusinessError;
+ Logger.error(TAG, `createPhotoOutputFn failed: ${JSON.stringify(err)}`);
+ return undefined;
+ }
+ }
+
+ // DocsCode 2
+ /**
+ * Creating a videoOutPut Output Object
+ */
+ createVideoOutputFn(cameraManager: camera.CameraManager, videoProfileObj: camera.VideoProfile,
+ surfaceId: string): camera.VideoOutput | undefined {
+ let videoOutput: camera.VideoOutput;
+ try {
+ videoOutput = cameraManager.createVideoOutput(videoProfileObj, surfaceId);
+ Logger.info(TAG, `createVideoOutputFn success: ${videoOutput}`);
+ return videoOutput;
+ } catch (error) {
+ let err = error as BusinessError;
+ Logger.error(TAG, `createVideoOutputFn failed: ${JSON.stringify(err)}`);
+ return undefined;
+ }
+ }
+
+ /**
+ * Creates a cameraInput output object
+ */
+ createCameraInputFn(cameraManager: camera.CameraManager,
+ cameraDevice: camera.CameraDevice): camera.CameraInput | undefined {
+ Logger.info(TAG, 'createCameraInputFn is called.');
+ let cameraInput: camera.CameraInput;
+ try {
+ cameraInput = cameraManager.createCameraInput(cameraDevice);
+ Logger.info(TAG, 'createCameraInputFn success');
+ return cameraInput;
+ } catch (error) {
+ let err = error as BusinessError;
+ Logger.error(TAG, `createCameraInputFn failed: ${JSON.stringify(err)}`);
+ return undefined;
+ }
+ }
+
+ /**
+ * Turn on the camera
+ */
+ async cameraInputOpenFn(cameraInput: camera.CameraInput): Promise {
+ let isOpenSuccess = false;
+ try {
+ await cameraInput.open();
+ isOpenSuccess = true;
+ Logger.info(TAG, 'cameraInput open success');
+ } catch (error) {
+ let err = error as BusinessError;
+ Logger.error(TAG, `createCameraInput failed : ${JSON.stringify(err)}`);
+ }
+ return isOpenSuccess;
+ }
+
+ /**
+ * Session Process
+ */
+ async sessionFlowFn(cameraManager: camera.CameraManager, cameraInput: camera.CameraInput,
+ previewOutput: camera.PreviewOutput, photoOutput: camera.PhotoOutput | undefined): Promise {
+ try {
+ // Creating a CaptureSession Instance
+ if (this.curSceneMode === camera.SceneMode.NORMAL_PHOTO) {
+ this.session = cameraManager.createSession(this.curSceneMode) as camera.PhotoSession;
+ } else if (this.curSceneMode === camera.SceneMode.NORMAL_VIDEO) {
+ this.session = cameraManager.createSession(this.curSceneMode) as camera.VideoSession;
+ }
+ if (this.session === undefined) {
+ return;
+ }
+ this.onSessionErrorChange(this.session);
+ // Start configuring session
+ this.session.beginConfig();
+ // Add CameraInput to the session.
+ this.session.addInput(cameraInput);
+ // Add previewOutput to the session.
+ this.session.addOutput(previewOutput);
+ if (photoOutput === undefined) {
+ return;
+ }
+ // Photographing and listening events
+ this.photoOutputCallBack(photoOutput);
+ // Add photoOutPut to the session.
+ this.session.addOutput(photoOutput);
+ // Submit the configuration information.
+ await this.session.commitConfig();
+ this.setFocusMode(camera.FocusMode.FOCUS_MODE_AUTO);
+ // Start Session Work
+ await this.session.start();
+ Logger.info(TAG, 'sessionFlowFn success');
+ } catch (error) {
+ let err = error as BusinessError;
+ Logger.error(TAG, `sessionFlowFn fail : ${JSON.stringify(err)}`);
+ }
+ }
+
+ /**
+ * Listening to photographing events
+ */
+ photoOutputCallBack(photoOutput: camera.PhotoOutput): void {
+ try {
+ // Listening and photographing start
+ photoOutput.on('captureStartWithInfo', (err: BusinessError, captureStartInfo: camera.CaptureStartInfo): void => {
+ if (err) {
+ Logger.error(TAG, `captureStartWithInfo err:${err.code}`);
+ return;
+ }
+ Logger.info(TAG, `photoOutputCallBack captureStartWithInfo success: ${JSON.stringify(captureStartInfo)}`);
+ });
+ // Monitors and captures the output of photographed frames
+ photoOutput.on('frameShutter', (err: BusinessError, frameShutterInfo: camera.FrameShutterInfo): void => {
+ if (err) {
+ Logger.error(TAG, `frameShutter err:${err.code}`);
+ return;
+ }
+ Logger.info(TAG, `photoOutputCallBack frameShutter captureId:
+ ${frameShutterInfo.captureId}, timestamp: ${frameShutterInfo.timestamp}`);
+ });
+ // Listening and photographing are complete
+ photoOutput.on('captureEnd', (err: BusinessError, captureEndInfo: camera.CaptureEndInfo): void => {
+ if (err) {
+ Logger.error(TAG, `captureEnd err:${err.code}`)
+ return;
+ }
+ Logger.info(TAG, `photoOutputCallBack captureEnd captureId:
+ ${captureEndInfo.captureId}, frameCount: ${captureEndInfo.frameCount}`);
+ });
+ // Listening and photographing are abnormal
+ photoOutput.on('error', (data: BusinessError): void => {
+ Logger.error(TAG, `photoOutPut data: ${JSON.stringify(data)}`);
+ });
+ if (this.photoMode === Constants.SUBSECTION_MODE) {
+ // DocsCode 5
+ photoOutput.on('photoAssetAvailable', (err: BusinessError, photoAsset: photoAccessHelper.PhotoAsset) => {
+ Logger.info(TAG, 'photoAssetAvailable begin');
+ if (err) {
+ Logger.error(TAG, `photoAssetAvailable err:${err.code}`);
+ return;
+ }
+ this.handlePhotoAssetCb(photoAsset);
+ });
+ // DocsCode 5
+ } else if (this.photoMode === Constants.SINGLE_STAGE_MODE) {
+ // DocsCode 4
+ photoOutput.on('photoAvailable', (err: BusinessError, photo: camera.Photo) => {
+ Logger.info(TAG, 'photoAvailable begin');
+ if (err) {
+ Logger.error(TAG, `photoAvailable err:${err.code}`);
+ return;
+ }
+ let imageObj: image.Image = photo.main;
+ imageObj.getComponent(image.ComponentType.JPEG, (err: BusinessError, component: image.Component) => {
+ Logger.info(TAG, `getComponent start`);
+ if (err) {
+ Logger.error(TAG, `getComponent err:${err.code}`);
+ return;
+ }
+ let buffer: ArrayBuffer = component.byteBuffer;
+ let imageSource: image.ImageSource = image.createImageSource(buffer);
+ imageSource.createPixelMap((err: BusinessError, pixelMap: image.PixelMap) => {
+ if (err) {
+ Logger.error(TAG, `createPixelMap err:${err.code}`);
+ return;
+ }
+ this.handlePhotoAssetCb(pixelMap);
+ });
+
+ });
+ })
+ // DocsCode 4
+ }
+ } catch (err) {
+ Logger.error(TAG, 'photoOutputCallBack error');
+ }
+ }
+
+ /**
+ * Listening for preview events
+ */
+ previewOutputCallBack(previewOutput: camera.PreviewOutput): void {
+ Logger.info(TAG, 'previewOutputCallBack is called');
+ try {
+ previewOutput.on('frameStart', (): void => {
+ Logger.debug(TAG, 'Preview frame started');
+ });
+ previewOutput.on('frameEnd', (): void => {
+ Logger.debug(TAG, 'Preview frame ended');
+ });
+ previewOutput.on('error', (previewOutputError: BusinessError): void => {
+ Logger.info(TAG, `Preview output previewOutputError: ${JSON.stringify(previewOutputError)}`);
+ });
+ } catch (err) {
+ Logger.error(TAG, 'previewOutputCallBack error');
+ }
+ }
+
+ /**
+ * Registers the callback function for changing the camera status.
+ * @param err - Error information
+ * @param cameraStatusInfo - Camera status information
+ * @returns No return value
+ */
+ registerCameraStatusChange(err: BusinessError, cameraStatusInfo: camera.CameraStatusInfo): void {
+ if (err) {
+ Logger.info(TAG, `registerCameraStatusChange err:${err.code}`)
+ return;
+ }
+ Logger.info(TAG, `cameraId: ${cameraStatusInfo.camera.cameraId},status: ${cameraStatusInfo.status}`);
+ }
+
+ /**
+ * Monitors camera status changes
+ * @param cameraManager - Camera Manager object
+ * @returns No return value
+ */
+ onCameraStatusChange(cameraManager: camera.CameraManager): void {
+ Logger.info(TAG, 'onCameraStatusChange is called');
+ try {
+ cameraManager.on('cameraStatus', this.registerCameraStatusChange);
+ } catch (error) {
+ Logger.error(TAG, 'onCameraStatusChange error');
+ }
+ }
+
+ /**
+ * Stop listening to camera status changes
+ * @returns No return value
+ */
+ offCameraStatusChange(): void {
+ Logger.info(TAG, 'offCameraStatusChange is called');
+ this.cameraManager?.off('cameraStatus', this.registerCameraStatusChange);
+ }
+
+ /**
+ * Listen for camera input changes
+ * @param cameraInput - Camera Input Object
+ * @param cameraDevice - Camera device object
+ * @returns No return value
+ */
+ onCameraInputChange(cameraInput: camera.CameraInput, cameraDevice: camera.CameraDevice): void {
+ Logger.info(TAG, `onCameraInputChange is called`);
+ try {
+ cameraInput.on('error', cameraDevice, (cameraInputError: BusinessError): void => {
+ Logger.info(TAG, `onCameraInputChange cameraInput error code: ${cameraInputError.code}`);
+ });
+ } catch (error) {
+ Logger.error(TAG, 'onCameraInputChange error');
+ }
+ }
+
+ /**
+ * Listening Capture Session Error Change
+ * @param session - Camera capture session object
+ * @returns No return value
+ */
+ onSessionErrorChange(session: camera.PhotoSession | camera.VideoSession): void {
+ try {
+ session.on('error', (captureSessionError: BusinessError): void => {
+ Logger.info(TAG,
+ 'onCaptureSessionErrorChange captureSession fail: ' + JSON.stringify(captureSessionError.code));
+ });
+ } catch (error) {
+ Logger.error(TAG, 'onCaptureSessionErrorChange error');
+ }
+ }
+
+ /**
+ * Focus mode
+ */
+ setFocusMode(focusMode: camera.FocusMode): void {
+ // Check whether the focus mode is supported
+ Logger.info(TAG, `setFocusMode is called`);
+ let isSupported = this.session?.isFocusModeSupported(focusMode);
+ Logger.info(TAG, `setFocusMode isSupported: ${isSupported}`);
+ // Setting the Focus Mode
+ if (!isSupported) {
+ return;
+ }
+ this.session?.setFocusMode(focusMode);
+ }
+}
+
+export default new CameraService();
\ No newline at end of file
diff --git a/SegmentedPhotograph/entry/src/main/ets/pages/EditPage.ets b/SegmentedPhotograph/entry/src/main/ets/pages/EditPage.ets
new file mode 100644
index 0000000000000000000000000000000000000000..f67fd4527d115f733a584f25f2bfa14abdf3a2d0
--- /dev/null
+++ b/SegmentedPhotograph/entry/src/main/ets/pages/EditPage.ets
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2024 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 { image } from '@kit.ImageKit';
+import { photoAccessHelper } from '@kit.MediaLibraryKit';
+import { router } from '@kit.ArkUI';
+import { BusinessError } from '@kit.BasicServicesKit';
+import Logger from '../common/utils/Logger';
+import { GlobalContext } from '../common/utils/GlobalContext';
+import { Constants } from '../common/Constants';
+
+const TAG: string = 'EditPage';
+
+class RequestImageParams {
+ context: Context | undefined = undefined;
+ photoAsset: photoAccessHelper.PhotoAsset | undefined = undefined;
+ callback: Function = () => {
+ };
+}
+
+@Entry
+@Component
+struct EditPage {
+ @State createPixelMapState: boolean = false;
+ @State curPixelMap: image.PixelMap | undefined = undefined;
+ @State photoUri: string = '';
+ @StorageLink('photoMode') photoMode: string = '';
+ private backIconLayoutWeight = 1;
+ private textLayoutWeight = 8;
+ // DocsCode 2
+ photoBufferCallback: (arrayBuffer: ArrayBuffer) => void = (arrayBuffer: ArrayBuffer) => {
+ Logger.info(TAG, 'photoBufferCallback is called');
+ let imageSource = image.createImageSource(arrayBuffer);
+ imageSource.createPixelMap((err: BusinessError, data: image.PixelMap) => {
+ if (err) {
+ Logger.info(TAG, `createPixelMap err:${err.code}`);
+ return;
+ }
+ Logger.info(TAG, 'createPixelMap is called');
+ this.curPixelMap = data;
+ });
+ };
+
+ requestImage(requestImageParams: RequestImageParams): void {
+ try {
+ class MediaDataHandler implements photoAccessHelper.MediaAssetDataHandler {
+ onDataPrepared(data: ArrayBuffer, map: Map): void {
+ Logger.info(TAG, 'onDataPrepared map' + JSON.stringify(map));
+ requestImageParams.callback(data);
+ Logger.info(TAG, 'onDataPrepared end');
+ }
+ };
+ let requestOptions: photoAccessHelper.RequestOptions = {
+ deliveryMode: photoAccessHelper.DeliveryMode.BALANCE_MODE,
+ };
+ const handler = new MediaDataHandler();
+ photoAccessHelper.MediaAssetManager.requestImageData(requestImageParams.context, requestImageParams.photoAsset,
+ requestOptions, handler);
+ } catch (error) {
+ Logger.error(TAG, `Failed in requestImage, error code: ${error.code}`);
+ }
+ }
+
+ aboutToAppear() {
+ Logger.info(TAG, 'aboutToAppear begin');
+ if (this.photoMode === Constants.SUBSECTION_MODE) {
+ let curPhotoAsset = GlobalContext.get().getT('photoAsset');
+ this.photoUri = curPhotoAsset.uri;
+ let requestImageParams: RequestImageParams = {
+ context: getContext(),
+ photoAsset: curPhotoAsset,
+ callback: this.photoBufferCallback
+ };
+ this.requestImage(requestImageParams);
+ Logger.info(TAG, `aboutToAppear photoUri: ${this.photoUri}`);
+ } else if (this.photoMode === Constants.SINGLE_STAGE_MODE) {
+ this.curPixelMap = GlobalContext.get().getT('photoAsset');
+ }
+ }
+
+ // DocsCode 2
+ build() {
+ Column() {
+ Column() {
+ // DocsCode 1
+ Image($r('app.media.ic_public_back'))
+ .objectFit(ImageFit.Cover)
+ .onClick(() => {
+ Logger.info(TAG, 'back onClick');
+ router.back();
+ })
+ .width(40)
+ .height(40)
+ // DocsCode 1
+ }
+ .padding({ left: Constants.BACK_ICON_MARGIN })
+ .width(Constants.FULL_PERCENT)
+ .layoutWeight(this.backIconLayoutWeight)
+ .alignItems(HorizontalAlign.Start)
+ .justifyContent(FlexAlign.Center)
+ .margin({ top: 44 })
+
+ Column() {
+ Image(this.curPixelMap)
+ .objectFit(ImageFit.Cover)
+ .width(Constants.FULL_PERCENT)
+ .height(Constants.EIGHTY_PERCENT)
+ }
+ .width(Constants.FULL_PERCENT)
+ .margin({ top: 68 })
+ .layoutWeight(this.textLayoutWeight)
+ }
+ .width(Constants.FULL_PERCENT)
+ .height(Constants.FULL_PERCENT)
+ .backgroundColor($r('app.color.dialog_background_color'))
+ }
+}
diff --git a/SegmentedPhotograph/entry/src/main/ets/pages/IndexPage.ets b/SegmentedPhotograph/entry/src/main/ets/pages/IndexPage.ets
new file mode 100644
index 0000000000000000000000000000000000000000..3d891b3fd77dc12834e76699a3315e5277b44012
--- /dev/null
+++ b/SegmentedPhotograph/entry/src/main/ets/pages/IndexPage.ets
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2024 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 { Constants } from '../common/Constants';
+import { router } from '@kit.ArkUI';
+
+@Entry
+@Component
+struct IndexPage {
+ build() {
+ Column() {
+ Text($r('app.string.camera_segment_photography'))
+ .width('100%')
+ .textAlign(TextAlign.Start)
+ .fontSize(32)
+ .lineHeight(40)
+ .fontWeight(700)
+ .margin({ top: 106 })
+ Blank()
+ Column() {
+ Button($r('app.string.single_segment_photography'))
+ .width('100%')
+ .fontSize(16)
+ .height(42)
+ .onClick(() => {
+ AppStorage.setOrCreate('photoMode', Constants.SINGLE_STAGE_MODE);
+ router.pushUrl({ url: 'pages/PhotoPage' });
+ })
+ Button($r('app.string.segmented_photography'))
+ .height(42)
+ .fontSize(16)
+ .margin({ top: 16 })
+ .width('100%')
+ .onClick(() => {
+ AppStorage.setOrCreate('photoMode', Constants.SUBSECTION_MODE);
+ router.pushUrl({ url: 'pages/PhotoPage' });
+ })
+
+ }.margin({ bottom: 46 })
+ }
+ .width('100%')
+ .height('100%')
+ .padding({ left: 16, right: 16 })
+ .backgroundColor('rgb(241, 243, 245)')
+ .alignItems(HorizontalAlign.Center)
+ }
+}
diff --git a/SegmentedPhotograph/entry/src/main/ets/pages/PhotoPage.ets b/SegmentedPhotograph/entry/src/main/ets/pages/PhotoPage.ets
new file mode 100644
index 0000000000000000000000000000000000000000..6b33721c852e7d38edf029b3b0805dc21ff2af1b
--- /dev/null
+++ b/SegmentedPhotograph/entry/src/main/ets/pages/PhotoPage.ets
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2024 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 CameraService from '../mode/CameraService';
+import Logger from '../common/utils/Logger';
+import { ModeComponent } from '../views/ModeComponent';
+import { GlobalContext } from '../common/utils/GlobalContext';
+import { Constants } from '../common/Constants';
+import { common } from '@kit.AbilityKit';
+import { window } from '@kit.ArkUI';
+
+const TAG = 'PhotoPage';
+let context = getContext(this) as common.UIAbilityContext;
+
+@Entry
+@Component
+struct PhotoPage {
+ @StorageLink('isShow') isShow: boolean = false;
+ @StorageLink('isOpenEditPage') isOpenEditPage: boolean = false;
+ @State xComponentAspectRatio: number = 1;
+ private mXComponentController: XComponentController = new XComponentController();
+ private defaultCameraDeviceIndex = 0;
+ private surfaceId = '';
+ private aiController: ImageAnalyzerController = new ImageAnalyzerController();
+ private options: ImageAIOptions = {
+ types: [ImageAnalyzerType.SUBJECT, ImageAnalyzerType.TEXT],
+ aiController: this.aiController
+ }
+ private windowClass: window.Window | undefined = undefined;
+
+ aboutToAppear(): void {
+ Logger.info(TAG, 'aboutToAppear');
+ }
+
+ async aboutToDisAppear(): Promise {
+ Logger.info(TAG, 'aboutToDisAppear');
+ }
+
+ async onPageShow(): Promise {
+ Logger.info(TAG, 'onPageShow');
+ try {
+ this.windowClass = context.windowStage.getMainWindowSync();
+ let SystemBarProperties: window.SystemBarProperties = {
+ statusBarContentColor: '#ffffff',
+ };
+ this.windowClass.setWindowSystemBarProperties(SystemBarProperties);
+ } catch (exception) {
+ console.error(`Failed to obtain the window. Cause code: ${exception.code}, message: ${exception.message}`);
+ }
+ this.isOpenEditPage = false;
+ if (this.surfaceId !== '' && !this.isOpenEditPage) {
+ await CameraService.initCamera(this.surfaceId, GlobalContext.get().getT('cameraDeviceIndex'));
+ }
+ }
+
+ async onPageHide(): Promise {
+ Logger.info(TAG, 'onPageHide');
+ if (!this.windowClass) {
+ return;
+ }
+ let SystemBarProperties: window.SystemBarProperties = {
+ statusBarContentColor: '#000000',
+ };
+ this.windowClass.setWindowSystemBarProperties(SystemBarProperties);
+ await CameraService.releaseCamera();
+ }
+
+ build() {
+ Column() {
+ if (this.isShow) {
+ // DocsCode 1
+ XComponent({
+ type: XComponentType.SURFACE,
+ controller: this.mXComponentController,
+ imageAIOptions: this.options
+ })
+ .onLoad(async () => {
+ Logger.info(TAG, 'onLoad is called');
+ this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
+ GlobalContext.get().setObject('cameraDeviceIndex', this.defaultCameraDeviceIndex);
+ GlobalContext.get().setObject('xComponentSurfaceId', this.surfaceId);
+ Logger.info(TAG, `onLoad surfaceId: ${this.surfaceId}`);
+ await CameraService.initCamera(this.surfaceId, this.defaultCameraDeviceIndex);
+ })
+ .border({
+ width: {
+ top: Constants.X_COMPONENT_BORDER_WIDTH,
+ bottom: Constants.X_COMPONENT_BORDER_WIDTH
+ },
+ color: Color.Black
+ })
+ .width('100%')
+ .height(523)
+ .margin({ top: 75, bottom: 72 })
+ // DocsCode 1
+ }
+ // Take picture
+ ModeComponent()
+ }
+ .size({
+ width: Constants.FULL_PERCENT,
+ height: Constants.FULL_PERCENT
+ })
+ .backgroundColor(Color.Black)
+ }
+}
diff --git a/SegmentedPhotograph/entry/src/main/ets/views/ModeComponent.ets b/SegmentedPhotograph/entry/src/main/ets/views/ModeComponent.ets
new file mode 100644
index 0000000000000000000000000000000000000000..89f21e6fffcc5d6903cc244de8c5761f6bbbf3e8
--- /dev/null
+++ b/SegmentedPhotograph/entry/src/main/ets/views/ModeComponent.ets
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2024 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 { router } from '@kit.ArkUI';
+import { photoAccessHelper } from '@kit.MediaLibraryKit';
+import CameraService from '../mode/CameraService';
+import Logger from '../common/utils/Logger';
+import { GlobalContext } from '../common/utils/GlobalContext';
+import { Constants } from '../common/Constants';
+import { image } from '@kit.ImageKit';
+
+const TAG: string = 'ModeComponent';
+
+@Component
+export struct ModeComponent {
+ @StorageLink('isOpenEditPage') @Watch('changePageState') isOpenEditPage: boolean = false;
+ @State isRecording: boolean = false;
+ @State isClick: boolean = false;
+
+ changePageState(): void {
+ if (this.isOpenEditPage) {
+ this.onJumpClick();
+ }
+ }
+
+ aboutToAppear(): void {
+ Logger.info(TAG, 'aboutToAppear');
+ CameraService.setSavePictureCallback(this.handleSavePicture);
+ }
+
+ // DocsCode 1
+ handleSavePicture = (photoAsset: photoAccessHelper.PhotoAsset | image.PixelMap): void => {
+ Logger.info(TAG, 'handleSavePicture');
+ this.setImageInfo(photoAsset);
+ AppStorage.set('isOpenEditPage', true);
+ Logger.info(TAG, 'setImageInfo end');
+ }
+
+ setImageInfo(photoAsset: photoAccessHelper.PhotoAsset | image.PixelMap): void {
+ Logger.info(TAG, 'setImageInfo');
+ GlobalContext.get().setObject('photoAsset', photoAsset);
+ }
+
+ // DocsCode 1
+ onJumpClick(): void {
+ router.pushUrl({ url: 'pages/EditPage' }, router.RouterMode.Single, (err) => {
+ if (err) {
+ Logger.error(TAG, `Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`);
+ return;
+ }
+ this.isClick = false
+ Logger.info(TAG, 'Invoke pushUrl succeeded.');
+ });
+ }
+
+ build() {
+ Column() {
+ Column() {
+ Row() {}
+ .width(54)
+ .height(54)
+ .backgroundColor(this.isClick ? 'rgb(232, 64, 38)' : 'rgb(255, 255, 255)')
+ .borderRadius(27)
+ .onClick(async () => {
+ this.isClick = true;
+ await CameraService.takePicture();
+ })
+ }
+ .width(80)
+ .height(80)
+ .justifyContent(FlexAlign.Center)
+ .alignItems(HorizontalAlign.Center)
+ .border({
+ width: 1,
+ color: Color.White,
+ radius: 40
+ })
+ }
+ .width(Constants.FULL_PERCENT)
+ .justifyContent(FlexAlign.Center)
+ .alignItems(HorizontalAlign.Center)
+ .height(Constants.TEN_PERCENT)
+ .padding({
+ left: Constants.CAPTURE_BUTTON_COLUMN_PADDING,
+ right: Constants.CAPTURE_BUTTON_COLUMN_PADDING
+ })
+ }
+}
\ No newline at end of file
diff --git a/SegmentedPhotograph/entry/src/main/module.json5 b/SegmentedPhotograph/entry/src/main/module.json5
new file mode 100644
index 0000000000000000000000000000000000000000..8fdc7beda5123312d0946b2d7d8367d3508f27e5
--- /dev/null
+++ b/SegmentedPhotograph/entry/src/main/module.json5
@@ -0,0 +1,48 @@
+{
+ "module": {
+ "name": "entry",
+ "type": "entry",
+ "description": "$string:module_desc",
+ "mainElement": "EntryAbility",
+ "deviceTypes": [
+ "phone"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false,
+ "pages": "$profile:main_pages",
+ "abilities": [
+ {
+ "name": "EntryAbility",
+ "srcEntry": "./ets/entryability/EntryAbility.ets",
+ "description": "$string:EntryAbility_desc",
+ "icon": "$media:layered_image",
+ "label": "$string:EntryAbility_label",
+ "startWindowIcon": "$media:startIcon",
+ "startWindowBackground": "$color:start_window_background",
+ "exported": true,
+ "skills": [
+ {
+ "entities": [
+ "entity.system.home"
+ ],
+ "actions": [
+ "action.system.home"
+ ]
+ }
+ ]
+ }
+ ],
+ "requestPermissions": [
+ {
+ "name": "ohos.permission.CAMERA",
+ "reason": "$string:reason",
+ "usedScene": {
+ "abilities": [
+ "EntryAbility"
+ ],
+ "when": "always"
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/SegmentedPhotograph/entry/src/main/resources/base/element/color.json b/SegmentedPhotograph/entry/src/main/resources/base/element/color.json
new file mode 100644
index 0000000000000000000000000000000000000000..b174be1ee5136b1e4dc6377fcb0b20fdda18c20b
--- /dev/null
+++ b/SegmentedPhotograph/entry/src/main/resources/base/element/color.json
@@ -0,0 +1,40 @@
+{
+ "color": [
+ {
+ "name": "start_window_background",
+ "value": "#FFFFFF"
+ },
+ {
+ "name": "white",
+ "value": "#FFFFFF"
+ },
+ {
+ "name": "theme_color",
+ "value": "#FF0034"
+ },
+ {
+ "name": "black",
+ "value": "#000000"
+ },
+ {
+ "name": "slider_track_color",
+ "value": "#99FFFFFF"
+ },
+ {
+ "name": "slide_text_font_color",
+ "value": "#182431"
+ },
+ {
+ "name": "border_color",
+ "value": "#FFFFFF"
+ },
+ {
+ "name": "dialog_background_color",
+ "value": "#F1F3F5"
+ },
+ {
+ "name": "flash_background_color",
+ "value": "#33FFFFFF"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/SegmentedPhotograph/entry/src/main/resources/base/element/string.json b/SegmentedPhotograph/entry/src/main/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..658d4442e22addb9991351b63a17c8c484f625d7
--- /dev/null
+++ b/SegmentedPhotograph/entry/src/main/resources/base/element/string.json
@@ -0,0 +1,60 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "module description"
+ },
+ {
+ "name": "EntryAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "EntryAbility_label",
+ "value": "cameraDemo"
+ },
+ {
+ "name": "reason",
+ "value": "Camera permission is required for the photo scene."
+ },
+ {
+ "name": "200px",
+ "value": "200px"
+ },
+ {
+ "name": "120px",
+ "value": "120px"
+ },
+ {
+ "name": "100px",
+ "value": "100px"
+ },
+ {
+ "name": "60px",
+ "value": "60px"
+ },
+ {
+ "name": "50px",
+ "value": "50px"
+ },
+ {
+ "name": "40px",
+ "value": "40px"
+ },
+ {
+ "name": "save",
+ "value": "save"
+ },
+ {
+ "name": "camera_segment_photography",
+ "value": "Camera segmented photography"
+ },
+ {
+ "name": "single_segment_photography",
+ "value": "Single-segment photography"
+ },
+ {
+ "name": "segmented_photography",
+ "value": "Segmented photo shooting"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/SegmentedPhotograph/entry/src/main/resources/base/media/background.png b/SegmentedPhotograph/entry/src/main/resources/base/media/background.png
new file mode 100644
index 0000000000000000000000000000000000000000..f939c9fa8cc8914832e602198745f592a0dfa34d
Binary files /dev/null and b/SegmentedPhotograph/entry/src/main/resources/base/media/background.png differ
diff --git a/SegmentedPhotograph/entry/src/main/resources/base/media/camera_clicked_button.png b/SegmentedPhotograph/entry/src/main/resources/base/media/camera_clicked_button.png
new file mode 100644
index 0000000000000000000000000000000000000000..82c5d65cf3e159039bfd933d34f0c7a54b88aa66
Binary files /dev/null and b/SegmentedPhotograph/entry/src/main/resources/base/media/camera_clicked_button.png differ
diff --git a/SegmentedPhotograph/entry/src/main/resources/base/media/camera_default_button.png b/SegmentedPhotograph/entry/src/main/resources/base/media/camera_default_button.png
new file mode 100644
index 0000000000000000000000000000000000000000..7793ce61846789c654f11e658bc75e208849c9a8
Binary files /dev/null and b/SegmentedPhotograph/entry/src/main/resources/base/media/camera_default_button.png differ
diff --git a/SegmentedPhotograph/entry/src/main/resources/base/media/foreground.png b/SegmentedPhotograph/entry/src/main/resources/base/media/foreground.png
new file mode 100644
index 0000000000000000000000000000000000000000..4483ddad1f079e1089d685bd204ee1cfe1d01902
Binary files /dev/null and b/SegmentedPhotograph/entry/src/main/resources/base/media/foreground.png differ
diff --git a/SegmentedPhotograph/entry/src/main/resources/base/media/ic_public_back.svg b/SegmentedPhotograph/entry/src/main/resources/base/media/ic_public_back.svg
new file mode 100644
index 0000000000000000000000000000000000000000..48e7964ee56948f4ef792f237475e39146431168
--- /dev/null
+++ b/SegmentedPhotograph/entry/src/main/resources/base/media/ic_public_back.svg
@@ -0,0 +1,8 @@
+
diff --git a/SegmentedPhotograph/entry/src/main/resources/base/media/icon.png b/SegmentedPhotograph/entry/src/main/resources/base/media/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c
Binary files /dev/null and b/SegmentedPhotograph/entry/src/main/resources/base/media/icon.png differ
diff --git a/SegmentedPhotograph/entry/src/main/resources/base/media/layered_image.json b/SegmentedPhotograph/entry/src/main/resources/base/media/layered_image.json
new file mode 100644
index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a
--- /dev/null
+++ b/SegmentedPhotograph/entry/src/main/resources/base/media/layered_image.json
@@ -0,0 +1,7 @@
+{
+ "layered-image":
+ {
+ "background" : "$media:background",
+ "foreground" : "$media:foreground"
+ }
+}
\ No newline at end of file
diff --git a/SegmentedPhotograph/entry/src/main/resources/base/media/startIcon.png b/SegmentedPhotograph/entry/src/main/resources/base/media/startIcon.png
new file mode 100644
index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b
Binary files /dev/null and b/SegmentedPhotograph/entry/src/main/resources/base/media/startIcon.png differ
diff --git a/SegmentedPhotograph/entry/src/main/resources/base/profile/backup_config.json b/SegmentedPhotograph/entry/src/main/resources/base/profile/backup_config.json
new file mode 100644
index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a
--- /dev/null
+++ b/SegmentedPhotograph/entry/src/main/resources/base/profile/backup_config.json
@@ -0,0 +1,3 @@
+{
+ "allowToBackupRestore": true
+}
\ No newline at end of file
diff --git a/SegmentedPhotograph/entry/src/main/resources/base/profile/main_pages.json b/SegmentedPhotograph/entry/src/main/resources/base/profile/main_pages.json
new file mode 100644
index 0000000000000000000000000000000000000000..bc351c7943683ecd7d6a811ec421658868f58649
--- /dev/null
+++ b/SegmentedPhotograph/entry/src/main/resources/base/profile/main_pages.json
@@ -0,0 +1,7 @@
+{
+ "src": [
+ "pages/PhotoPage",
+ "pages/EditPage",
+ "pages/IndexPage"
+ ]
+}
\ No newline at end of file
diff --git a/SegmentedPhotograph/entry/src/main/resources/en_US/element/string.json b/SegmentedPhotograph/entry/src/main/resources/en_US/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..658d4442e22addb9991351b63a17c8c484f625d7
--- /dev/null
+++ b/SegmentedPhotograph/entry/src/main/resources/en_US/element/string.json
@@ -0,0 +1,60 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "module description"
+ },
+ {
+ "name": "EntryAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "EntryAbility_label",
+ "value": "cameraDemo"
+ },
+ {
+ "name": "reason",
+ "value": "Camera permission is required for the photo scene."
+ },
+ {
+ "name": "200px",
+ "value": "200px"
+ },
+ {
+ "name": "120px",
+ "value": "120px"
+ },
+ {
+ "name": "100px",
+ "value": "100px"
+ },
+ {
+ "name": "60px",
+ "value": "60px"
+ },
+ {
+ "name": "50px",
+ "value": "50px"
+ },
+ {
+ "name": "40px",
+ "value": "40px"
+ },
+ {
+ "name": "save",
+ "value": "save"
+ },
+ {
+ "name": "camera_segment_photography",
+ "value": "Camera segmented photography"
+ },
+ {
+ "name": "single_segment_photography",
+ "value": "Single-segment photography"
+ },
+ {
+ "name": "segmented_photography",
+ "value": "Segmented photo shooting"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/SegmentedPhotograph/entry/src/main/resources/zh_CN/element/string.json b/SegmentedPhotograph/entry/src/main/resources/zh_CN/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..2575124005dd2400c47bd97c0d6548691e7a7683
--- /dev/null
+++ b/SegmentedPhotograph/entry/src/main/resources/zh_CN/element/string.json
@@ -0,0 +1,60 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "模块描述"
+ },
+ {
+ "name": "EntryAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "EntryAbility_label",
+ "value": "cameraDemo"
+ },
+ {
+ "name": "reason",
+ "value": "拍照场景需要相机权限"
+ },
+ {
+ "name": "200px",
+ "value": "200px"
+ },
+ {
+ "name": "120px",
+ "value": "120px"
+ },
+ {
+ "name": "100px",
+ "value": "100px"
+ },
+ {
+ "name": "60px",
+ "value": "60px"
+ },
+ {
+ "name": "50px",
+ "value": "50px"
+ },
+ {
+ "name": "40px",
+ "value": "40px"
+ },
+ {
+ "name": "save",
+ "value": "保存"
+ },
+ {
+ "name": "camera_segment_photography",
+ "value": "相机分段式拍照"
+ },
+ {
+ "name": "single_segment_photography",
+ "value": "单段式拍照"
+ },
+ {
+ "name": "segmented_photography",
+ "value": "分段式拍照"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/SegmentedPhotograph/hvigor/hvigor-config.json5 b/SegmentedPhotograph/hvigor/hvigor-config.json5
new file mode 100644
index 0000000000000000000000000000000000000000..06b2783670a348f95533b352c1ceda909a842bbc
--- /dev/null
+++ b/SegmentedPhotograph/hvigor/hvigor-config.json5
@@ -0,0 +1,22 @@
+{
+ "modelVersion": "5.0.0",
+ "dependencies": {
+ },
+ "execution": {
+ // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */
+ // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */
+ // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */
+ // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */
+ // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */
+ },
+ "logging": {
+ // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
+ },
+ "debugging": {
+ // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */
+ },
+ "nodeOptions": {
+ // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/
+ // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/
+ }
+}
diff --git a/SegmentedPhotograph/hvigorfile.ts b/SegmentedPhotograph/hvigorfile.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f3cb9f1a87a81687554a76283af8df27d8bda775
--- /dev/null
+++ b/SegmentedPhotograph/hvigorfile.ts
@@ -0,0 +1,6 @@
+import { appTasks } from '@ohos/hvigor-ohos-plugin';
+
+export default {
+ system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
+ plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
+}
diff --git a/SegmentedPhotograph/oh-package.json5 b/SegmentedPhotograph/oh-package.json5
new file mode 100644
index 0000000000000000000000000000000000000000..0cec98259052cc175d04dbcf61780de4bc9785f4
--- /dev/null
+++ b/SegmentedPhotograph/oh-package.json5
@@ -0,0 +1,7 @@
+{
+ "modelVersion": "5.0.0",
+ "description": "Please describe the basic information.",
+ "dependencies": {
+ },
+ "devDependencies": {}
+}
diff --git a/SegmentedPhotograph/screenshoots/devices/1.gif b/SegmentedPhotograph/screenshoots/devices/1.gif
new file mode 100644
index 0000000000000000000000000000000000000000..21f2d8bd552aca096680eedba4a046420c40ea16
Binary files /dev/null and b/SegmentedPhotograph/screenshoots/devices/1.gif differ
diff --git a/SegmentedPhotograph/screenshoots/devices/2.gif b/SegmentedPhotograph/screenshoots/devices/2.gif
new file mode 100644
index 0000000000000000000000000000000000000000..d7636131c87583f17a59bfd0fb71e070121977be
Binary files /dev/null and b/SegmentedPhotograph/screenshoots/devices/2.gif differ