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 @@ + + + Created with Pixso. + + + + + 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