Intelligent Assistant
Chat with our virtual assistant to get answers promptly.
This solution is tailored for image and text creation in social communication applications, leveraging HarmonyOS features like seamless hopping and interactive services.
The process starts with selecting local images via Photo Picker, followed by intelligent image processing. Users can also capture videos using a custom camera. During text creation, the workflow supports seamless continuation of editing and cross-device access to Gallery or camera content. Users can seamlessly continue editing and photo access across devices.
The process of editing images and texts is as follows.

This solution is ideal for social communication applications, outlining how to integrate HarmonyOS features for image and text creation. It offers a detailed technical implementation guide to accelerate application development.
This solution combines HarmonyOS features such as device interconnection, intelligent processing, and hopping to improve user experience when posting contents online. The detailed advantages are as follows:
(1) Device interconnection: allows users to flexibly select media resources from various devices and take photos using different devices, simplifying the process of data transmission while providing a more convenient experience.
(2) HarmonyOS AI: provides powerful support for creation. It enables users to extract information from images for text composition and remove backgrounds to extract objects for secondary creation, working wonders on editing.
(3) Hopping: enables seamless work across devices and synchronizes the latest editing status to the new device, allowing users to continue their work on the most suitable device.
Scenario | Description | Implementation |
Image selection | Select a resource file type on the release page | Photo Picker for image selection |
Photo shoot | Take and preview moving photos on the custom camera page. | Camera component for custom camera |
Image text recognition, cropping, and HDR Vivid image display | Select an image on the image browsing page to crop objects, and copy the texts on the image for text content creation. Automatically identify the HDR mode and display highlights. | Image component for OCR and cropping |
Cross-device photo selection | Select an image from another device and send it back to the local device. | CollaborationService component |
Hopping for the editing page | Continue editing on different devices. | Hopping capability for continuous editing across devices via ArkData and distributed file management |
When a user initiates the release process from the home page, a semi-modal Picker page is displayed, which can be customized to provide more options.
The system Picker does not require the READ_IMAGEVIDEO and WRITE_IMAGEVIDEO permissions.
Create a PhotoViewPicker instance first.
- const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
Set the attributes of image selection based on the service logic, for example, set the media resource type and the maximum number of resources.
- const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
- photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
- photoSelectOptions.maxSelectNumber = CommonConstants.LIMIT_PICKER_NUM - selectedNum;
- photoViewPicker.select(photoSelectOptions).then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
- let uriArr = photoSelectResult.photoUris;
- callback(uriArr);
- }).catch((err: BusinessError) => {
- Logger.error(UIUtils.tag,
- `Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
- });
After selecting an image, users can browse it, press and hold an object to extract it, or recognize text for further editing. If the image is taken in HDR Vivid mode, the enhanced visual effect is displayed.
Long press the image for text recognition and object cropping

(1) Set the enableAnalyzer attribute of the Image component to implement Optical character Recognition (OCR) and cropping. Set the dynamicRangeMode attribute to enable HDR, which must be used together with image.DecodingOptions to configure the dynamic range mode.
(2) OCR: If text can be recognized from an image, users can tap the recognition button on the image or long press the text to display a shortcut menu for copying or selecting text.
(3) Cropping: Long press an object in the image to crop it. You can copy and share the cropped image from the menu.
Enable AI image analysis and set the dynamic range mode of the image.
- Image(item)
- .objectFit(ImageFit.Contain)
- .enableAnalyzer(true)
- .dynamicRangeMode(DynamicRangeMode.HIGH)
Set image decoding options, which is used together with the dynamic range mode.
- public static options: image.DecodingOptions = {
- index: 0,
- editable: false,
- desiredPixelFormat: image.PixelMapFormat.RGBA_8888,
- };
-
-
- static createPixelMap(uri: string): ImageInfo | undefined {
- let imageInfo: ImageInfo | undefined;
- try {
- let file = fileIo.openSync(uri, fileIo.OpenMode.READ_ONLY);
- let displayName = file.name;
- let imageResource = image.createImageSource(file.fd);
- let pixelMap = imageResource.createPixelMapSync(FileUtils.options);
- imageInfo = { imagePixelMap: pixelMap, imageName: displayName };
- fileIo.closeSync(file);
- } catch (error) {
- Logger.error(FileUtils.tag, `createPixelMap error: ${JSON.stringify(error)}`);
- }
- return imageInfo;
- }
On the image editing page, add a custom camera tab to display diverse photos taken in different modes.
(1) After the camera is initialized, users can enable/disable the moving photo feature (disabled by default). If this feature is enabled, you can tap the Moving Photo button to switch the status.
(2) A photo is displayed 30 seconds after it is taken.
(3) MovingPhotoView is required for previewing a photo; users can long press the photo to play it.
(4) Apply for necessary permissions. The camera can be initialized after the permissions are granted.
Apply for the required permissions.
- private permissions: Array<Permissions> = [
- 'ohos.permission.CAMERA',
- 'ohos.permission.MICROPHONE',
- 'ohos.permission.MEDIA_LOCATION',
- 'ohos.permission.READ_IMAGEVIDEO',
- 'ohos.permission.WRITE_IMAGEVIDEO',
- ];
- abilityAccessCtrl.createAtManager().requestPermissionsFromUser(DataUtils.context, this.permissions).then(() => {
- this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
- this.initCamera();
- this.getThumbnail();
- })
Set the Moving Photo attribute of the camera.
- setEnableLivePhoto(isMovingPhoto: boolean) {
- try {
- if (this.photoOutput?.isMovingPhotoSupported()) {
- this.photoOutput?.enableMovingPhoto(isMovingPhoto);
- }
- } catch (error) {
- Logger.error(this.tag, `The setEnableLivePhoto call failed. error: ${JSON.stringify(error)}`);
- }
- }
- async getThumbnail(): Promise<void> {
- try {
- let photoAsset: photoAccessHelper.PhotoAsset =
- AppStorage.get(CommonConstants.KEY_PHOTO_ASSET) as photoAccessHelper.PhotoAsset;
- if (photoAsset === undefined) {
- return;
- }
- this.currentImg = await photoAsset.getThumbnail();
- } catch (error) {
- Logger.error(this.tag, `getThumbnail error: ${JSON.stringify(error)}`);
- }
- }
Import the Moving Photo library.
- import { MovingPhotoView, MovingPhotoViewController, MovingPhotoViewAttribute } from '@ohos.multimedia.movingphotoview';
Request the moving photo feature through photoAccessHelper.PhotoAsset.
- @StorageLink(CommonConstants.KEY_MOVING_DATA) src: photoAccessHelper.MovingPhoto | undefined = undefined;
- @StorageLink(CommonConstants.KEY_IMAGE_INFO) imageInfoArr: Array<ImageInfo> = [];
- @State isMuted: boolean = false;
- async aboutToAppear(): Promise<void> {
- // ...
- this.requestMovingPhoto();
- }
-
- private requestMovingPhoto() {
- let photoAsset: photoAccessHelper.PhotoAsset =
- AppStorage.get(CommonConstants.KEY_PHOTO_ASSET) as photoAccessHelper.PhotoAsset;
- if (photoAsset === undefined) {
- return;
- }
- let requestOptions: photoAccessHelper.RequestOptions = {
- deliveryMode: photoAccessHelper.DeliveryMode.FAST_MODE,
- }
- photoAccessHelper.MediaAssetManager.requestMovingPhoto(DataUtils.context, photoAsset, requestOptions,
- new MediaDataHandlerMovingPhoto()).catch(() => {
- Logger.error(this.tag, `requestMovingPhoto fail!`);
- });
- }
-
- class MediaDataHandlerMovingPhoto implements photoAccessHelper.MediaAssetDataHandler<photoAccessHelper.MovingPhoto> {
- async onDataPrepared(movingPhoto: photoAccessHelper.MovingPhoto): Promise<void> {
- AppStorage.setOrCreate(CommonConstants.KEY_MOVING_DATA, movingPhoto);
- }
- }
Add a moving photo display.
- build() {
- Flex({
- direction: new BreakpointType(
- {
- sm: FlexDirection.Column,
- md: FlexDirection.Column,
- lg: FlexDirection.Row,
- }
- ).getValue(this.currentBreakpoint),
- wrap: FlexWrap.NoWrap,
- justifyContent: FlexAlign.Start,
- alignItems: ItemAlign.Start,
- alignContent: FlexAlign.Start
- }) {
- this.setActions();
- MovingPhotoView({
- movingPhoto: this.src,
- controller: this.controller
- })
- .width($r('app.string.full_screen'))
- .objectFit(ImageFit.Contain)
- .muted(this.isMuted)
- .margin(new BreakpointType(
- {
- sm: { bottom: $r('app.float.margin_190') } as Padding,
- md: { bottom: $r('app.float.margin_190') } as Padding,
- lg: { right: $r('app.float.margin_24') } as Padding,
- }
- ).getValue(this.currentBreakpoint))
- }
- .backgroundColor(Color.Black)
- .width($r('app.string.full_screen'))
- .height($r('app.string.full_screen'))
- }
This section describes only the core code of the main process. Customizing a camera requires other configurations, for which you can refer to the CameraService file. To learn about how to use the moving photo feature, see the related API description @ohos.multimedia.movingphotoview (MovingPhotoView).
Cross-device image capture

The CollaborationService component enables cross-device access to albums, camera, and images, facilitating image transmission between devices.
The CollaborationService component requires a network connection and the same login account.
Currently, the CollaborationService component allows only the heavy devices (not portable) to invoke light devices (portable). For example:
(1) The tablet can invoke a phone rather than another tablet.
(2) The phone cannot invoke a tablet or another phone.
Taking photos and accessing albums across devices
Use createCollaborationServiceMenuItems to define the device list picker and display the list of devices with the camera capability in the network.
- import {
- CollaborationServiceFilter,
- CollaborationServiceStateDialog,
- createCollaborationServiceMenuItems
- } from '@kit.ServiceCollaborationKit';
- @Builder
- CollaborationMenu() {
- Menu() {
- createCollaborationServiceMenuItems([CollaborationServiceFilter.ALL]);
- }
- }
Use the CollaborationCameraStateDialog component to display a dialog box to show the camera status on the target device.
This component is directly called in the build() function to implement the onState method. After the shooting is complete, the content is returned through the onState method.
The callback function of the onState method contains two parameters: stateCode (which indicates the service completion status), and buffer (which indicates the data returned upon success).
- @Builder
- setCollaborationDialog() {
- CollaborationServiceStateDialog({
- onState: (stateCode: number, bufferType: string, buffer: ArrayBuffer): void => this.doInsertPicture(stateCode,
- bufferType, buffer)
- });
- }
-
- private doInsertPicture(stateCode: number, bufferType: string, buffer: ArrayBuffer): void {
- if (stateCode !== 0) {
- Logger.error(this.tag, `doInsertPicture stateCode: ${stateCode}}`);
- return;
- }
- Logger.info(this.tag, `doInsertPicture bufferType: ${bufferType}}`);
- if (bufferType === CommonConstants.BUFFER_TYPE) {
- if (this.imageInfoArr.length === CommonConstants.LIMIT_PICKER_NUM) {
- try {
- this.getUIContext().getPromptAction().showToast({
- message: $r('app.string.toast_picker_limit'),
- duration: DataUtils.fromResToNumber($r('app.float.show_DELAY_TIME')),
- });
- } catch (error) {
- Logger.error(this.tag, `showToast error: ${JSON.stringify(error)}}`);
- }
- return;
- }
- let saveUri: string = FileUtils.saveFile(DataUtils.context, buffer);
- let imageInfo: ImageInfo | undefined = FileUtils.createPixelMap(saveUri);
- if (imageInfo) {
- this.imageInfoArr.unshift(imageInfo);
- this.selectedData.unshiftData(imageInfo.imagePixelMap);
- // copy file to distributedFilesDir
- FileUtils.copyFileToDestination(saveUri, DataUtils.context.distributedFilesDir);
- }
- }
- }
The hopping capability allows users to continue editing on other devices.
Editing continuation enabled

Prerequisites:
(1) The same HUAWEI ID is logged on two devices.
(2) Both devices enable Wi-Fi and Bluetooth and connect to the same LAN for faster data transmission.
(3) Application continuation can be triggered only within the same application (UIAbility), and the application must be available on both devices.
(4) The data transmitted by using wantParam in the onContinue callback must be less than 100 KB. For a large amount of data, such as images, the distributed data objects or distributed file system is required.
To request permissions, set the requestPermissions attribute of the module object in the module.json5 file.
- "requestPermissions": [
- {
- "name": "ohos.permission.DISTRIBUTED_DATASYNC",
- "reason": "$string:distributed_desc",
- "usedScene": {
- "abilities": [
- "EntryAbility"
- ],
- "when": "always"
- }
- }
- // ...
- ]
- "abilities": [
- {
- // ...
- "continuable": true,
- // ...
- }
- ]
You can migrate the route stack on demand or dynamically configure the route stack to enable continuation only for specific pages. The following sample code uses GraphicCreationPage as an example. For details about how to migrate the route stack on demand, see Migrating the Page Stack on Demand.
- onPageShow(): void {
- DataUtils.context.setMissionContinueState(AbilityConstant.ContinueState.ACTIVE, (result) => {
- Logger.info('setMissionContinueState ACTIVE result: ', `${result.code}`);
- });
- }
-
- aboutToDisappear(): void {
- this.title = '';
- this.description = '';
- DataUtils.context.setMissionContinueState(AbilityConstant.ContinueState.INACTIVE, (result) => {
- Logger.info('setMissionContinueState INACTIVE result: ', `${result.code}`);
- });
- }
Specify the page to enable continuation.
- onWindowStageRestore(windowStage: window.WindowStage) {
- windowStage.loadContent('pages/GraphicCreationPage', (err, data) => {
- // ...
- });
- }
- async onContinue(wantParam: Record<string, Object | undefined>): Promise<AbilityConstant.OnContinueResult> {
- try {
- // get distribute id
- let sessionId: string = distributedDataObject.genSessionId();
- wantParam.distributedSessionId = sessionId;
- // set images assets info
- let imageInfoArray = AppStorage.get<Array<ImageInfo>>(CommonConstants.KEY_IMAGE_INFO);
- let assets: commonType.Assets = [];
- if (imageInfoArray) {
- for (let i = 0; i < imageInfoArray.length; i++) {
- let append = imageInfoArray[i];
- let attachment: commonType.Asset | undefined = this.getAssetInfo(append);
- if (attachment === undefined) {
- continue;
- }
- assets.push(attachment);
- }
- }
- // set distribute data object
- let contentInfo: ContentInfo = new ContentInfo(
- AppStorage.get(CommonConstants.KEY_TITLE),
- AppStorage.get(CommonConstants.KEY_DESCRIPTION),
- AppStorage.get(CommonConstants.KEY_IMAGE_INFO),
- assets
- );
- let source = contentInfo.flatAssets();
- // save data to distribute
- this.distributedObject = distributedDataObject.create(this.context, source);
- Logger.info(this.tag, `onContinue source: ${JSON.stringify(source)}`);
- this.distributedObject.setSessionId(sessionId);
- await this.distributedObject.save(wantParam.targetDevice as string).catch((err: BusinessError) => {
- Logger.error(this.tag, `Failed to save. Code: ${err.code}, message: ${err.message}`);
- });
- } catch (error) {
- Logger.error(this.tag, 'distributedDataObject failed', `code ${(error as BusinessError).code}`);
- }
- return AbilityConstant.OnContinueResult.AGREE;
- }
-
- private getAssetInfo(append: ImageInfo): commonType.Asset | undefined {
- let filePath = this.context.distributedFilesDir + '/' + append.imageName;
- try {
- fileIo.statSync(filePath);
- let uri: string = fileUri.getUriFromPath(filePath);
- let stat = fileIo.statSync(filePath);
- let attachment: commonType.Asset = {
- name: append.imageName,
- uri: uri,
- path: filePath,
- createTime: stat.ctime.toString(),
- modifyTime: stat.ctime.toString(),
- size: stat.size.toString()
- };
- Logger.info(this.tag, `getAssetInfo attachment = ${JSON.stringify(attachment)}`);
- return attachment;
- } catch (error) {
- Logger.error(this.tag, `getAssetInfo error: ${JSON.stringify(error)}`);
- return undefined;
- }
- }
The code is written in EntryAbility instead of Page. AppStorage can be used to continuously store the data and bind the data bidirectionally. When the data changes, the view is changed.
Implement the onCreate and onNewWant APIs on the target device. The onCreate API is called during cold start or during hot start in multiton mode. The onNewWant API is called during hot start in singleton mode. Register a data listener and restore the data of page transition only when the application is in the continuation state.
- onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
- DataUtils.context = this.context;
- // set circulation status INACTIVE
- this.context.setMissionContinueState(AbilityConstant.ContinueState.INACTIVE, (result) => {
- Logger.info(`restoreDistributedObject setMissionContinueState code: ${result.code}`);
- });
- this.restoreDistributedObject(want, launchParam);
- Logger.info(this.tag, '%{public}s', 'Ability onCreate');
- }
-
- onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
- this.restoreDistributedObject(want, launchParam);
- }
-
- private restoreDistributedObject(want: Want, launchParam: AbilityConstant.LaunchParam): void {
- if (launchParam.launchReason !== AbilityConstant.LaunchReason.CONTINUATION) {
- return;
- }
- try {
- // File copying takes a long time, resulting in the page lifecycle aboutToAppear being executed first.
- let imageInfoArr: ImageInfo[] = [];
- AppStorage.setOrCreate(CommonConstants.KEY_IMAGE_INFO, imageInfoArr);
- let contentInfo: ContentInfo = new ContentInfo(undefined, undefined, undefined, undefined);
- // Create a distributed data object.
- this.distributedObject = distributedDataObject.create(this.context, contentInfo);
- // Add a data restored listener.
- this.distributedObject.on('status',
- (_sessionId: string, _networkId: string, status: 'online' | 'offline' | 'restored') => {
- if (status === 'restored') {
- if (!this.distributedObject) {
- return;
- }
- AppStorage.setOrCreate(CommonConstants.KEY_TITLE, this.distributedObject['title']);
- AppStorage.setOrCreate(CommonConstants.KEY_DESCRIPTION, this.distributedObject['description']);
- let attachments = this.distributedObject['attachments'] as commonType.Assets;
- if (attachments) {
- for (const attachment of attachments) {
- let sourceUri: string =
- fileUri.getUriFromPath(`${this.context.distributedFilesDir}/${attachment.name}`);
- let destination: string = this.context.filesDir;
- FileUtils.copyFileToDestination(sourceUri, destination);
- let uri: string = `${this.context.filesDir}/${attachment.name}`;
- let imageInfo = FileUtils.createPixelMap(uri);
- if (imageInfo) {
- imageInfoArr.push(imageInfo);
- }
- }
- }
- AppStorage.set(CommonConstants.KEY_IMAGE_INFO, imageInfoArr);
- AppStorage.setOrCreate(CommonConstants.KEY_RESTORE_IMAGE_INFO, imageInfoArr);
- }
- });
- let sessionId: string = want.parameters?.distributedSessionId as string;
- this.distributedObject.setSessionId(sessionId);
- this.context.restoreWindowStage(new LocalStorage());
- } catch (error) {
- Logger.info(`restoreDistributedObject error: ${JSON.stringify(error)}`);
- }
- }
The image transfer requires the support from the distributed file system. After the sender saves the file to the distribution directory, the receiver can copy this file from the distribution directory to the local sandbox.
- static copyFileToDestination(sourceUri: string, destination: string) {
- try {
- let buf = new ArrayBuffer(CommonConstants.FILE_BUFFER_SIZE);
- let readSize = 0;
- let file = fileIo.openSync(sourceUri, fileIo.OpenMode.READ_ONLY);
- let readLen = fileIo.readSync(file.fd, buf, { offset: readSize });
- let destinationDistribute =
- fileIo.openSync(`${destination}/${file.name}`, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
- while (readLen > 0) {
- readSize += readLen;
- fileIo.writeSync(destinationDistribute.fd, buf);
- readLen = fileIo.readSync(file.fd, buf, { offset: readSize });
- }
- Logger.info(FileUtils.tag, 'copyFileToDestination success');
- fileIo.closeSync(file);
- fileIo.closeSync(destinationDistribute);
- } catch (err) {
- Logger.error(FileUtils.tag, `copyFileToDestination failed. Code: ${err.code}, message: ${err.message}`);
- }
- }