• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Using startAbilityByType to Start an Image Editing Application
2## When to Use
3If an application does not have the image editing capability but needs to edit an image, it can call **startAbilityByType** to start the vertical domain panel that displays available image editing applications, which can be used to edit the image. An image editing application can use the PhotoEditorExtensionAbility to implement an image editing page and register the page with the image editing panel. In this way, its image editing capability is opened to other applications.
4
5The following figure shows the process.
6
7![](figures/photoEditorExtensionAbility.png)
8
9For example, when a user chooses to edit an image in Gallery, the Gallery application can call **startAbilityByType** to start the image editing application panel. The user can choose an application that has implemented the PhotoEditorExtensionAbility to edit the image.
10
11## Available APIs
12
13For details about the APIs, see [PhotoEditorExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-photoEditorExtensionAbility.md) and [PhotoEditorExtensionContext](../reference/apis-ability-kit/js-apis-app-ability-photoEditorExtensionContext.md).
14
15| **API** | **Description**|
16| -------- | -------- |
17| onStartContentEditing(uri: string, want:Want, session: UIExtensionContentSession):void       | Called when content editing starts. Operations such as reading original images and loading pages can be performed in the callback.|
18| saveEditedContentWithImage(pixeMap: image.PixelMap, option: image.PackingOption): Promise\<AbilityResult\>  | Saves the passed-in PixelMap object, which is an edited image.  |
19
20## Target Application (Image Editing Application): Implementing an Image Editing Page
21
221. Manually create a PhotoEditorExtensionAbility in the DevEco Studio project.
23    1. In the **ets** directory of the target module, right-click and choose **New > Directory** to create a directory named **PhotoEditorExtensionAbility**.
24    2. In the **PhotoEditorExtensionAbility** directory, right-click and choose **New > File** to create an .ets file, for example, **ExamplePhotoEditorAbility.ets**.
252. Override the lifecycle callbacks of **onCreate**, **onForeground**, **onBackground**, **onDestroy**, and **onStartContentEditing** in the **ExamplePhotoEditorAbility.ets** file.
26
27    Load the entry page file **pages/Index.ets** in **onStartContentEditing**, and save the session, URI, and instance objects in the LocalStorage, which passes them to the page.
28
29    ```ts
30    import { PhotoEditorExtensionAbility,UIExtensionContentSession,Want } from '@kit.AbilityKit';
31    import { hilog } from '@kit.PerformanceAnalysisKit';
32
33    const TAG = '[ExamplePhotoEditorAbility]';
34    export default class ExamplePhotoEditorAbility extends PhotoEditorExtensionAbility {
35      onCreate() {
36        hilog.info(0x0000, TAG, 'onCreate');
37      }
38
39      // Obtain an image, load the page, and pass the required parameters to the page.
40      onStartContentEditing(uri: string, want: Want, session: UIExtensionContentSession): void {
41        hilog.info(0x0000, TAG, `onStartContentEditing want: ${JSON.stringify(want)}, uri: ${uri}`);
42
43        const storage: LocalStorage = new LocalStorage({
44          "session": session,
45          "uri": uri
46        } as Record<string, Object>);
47
48        session.loadContent('pages/Index', storage);
49      }
50
51      onForeground() {
52        hilog.info(0x0000, TAG, 'onForeground');
53      }
54
55      onBackground() {
56        hilog.info(0x0000, TAG, 'onBackground');
57      }
58
59      onDestroy() {
60        hilog.info(0x0000, TAG, 'onDestroy');
61      }
62    }
63
64    ```
653. Implement image editing in the page.
66
67    After image editing is complete, call **saveEditedContentWithImage** to save the image and return the callback result to the caller through **terminateSelfWithResult**.
68
69    ```ts
70    import { common } from '@kit.AbilityKit';
71    import { UIExtensionContentSession, Want } from '@kit.AbilityKit';
72    import { hilog } from '@kit.PerformanceAnalysisKit';
73    import { fileIo } from '@kit.CoreFileKit';
74    import { image } from '@kit.ImageKit';
75
76    const TAG = '[ExamplePhotoEditorAbility]';
77
78    @Entry
79    @Component
80    struct Index {
81      @State message: string = 'editImg';
82      @State originalImage: PixelMap | null = null;
83      @State editedImage: PixelMap | null = null;
84      private newWant ?: Want;
85      private storage = this.getUIContext().getSharedLocalStorage();
86
87      aboutToAppear(): void {
88        let originalImageUri = this.storage?.get<string>("uri") ?? "";
89        hilog.info(0x0000, TAG, `OriginalImageUri: ${originalImageUri}.`);
90
91        this.readImageByUri(originalImageUri).then(imagePixMap => {
92          this.originalImage = imagePixMap;
93        })
94      }
95
96      // Read the image based on the URI.
97      async readImageByUri(uri: string): Promise < PixelMap | null > {
98        hilog.info(0x0000, TAG, "uri: " + uri);
99        let file: fileIo.File | undefined;
100        try {
101          file = await fileIo.open(uri, fileIo.OpenMode.READ_ONLY);
102          hilog.info(0x0000, TAG, "Original image file id: " + file.fd);
103
104          let imageSourceApi: image.ImageSource = image.createImageSource(file.fd);
105          if(!imageSourceApi) {
106            hilog.info(0x0000, TAG, "ImageSourceApi failed");
107            return null;
108          }
109          let pixmap: image.PixelMap = await imageSourceApi.createPixelMap();
110          if(!pixmap) {
111            hilog.info(0x0000, TAG, "createPixelMap failed");
112            return null;
113          }
114          this.originalImage = pixmap;
115          return pixmap;
116        } catch(e) {
117          hilog.error(0x0000, TAG, `ReadImage failed:${e}`);
118        } finally {
119          fileIo.close(file);
120        }
121        return null;
122      }
123
124      build() {
125        Row() {
126          Column() {
127            Text(this.message)
128              .fontSize(50)
129              .fontWeight(FontWeight.Bold)
130
131            Button("RotateAndSaveImg").onClick(event => {
132              hilog.info(0x0000, TAG, `Start to edit image and save.`);
133              // Implement image editing.
134              this.originalImage?.rotate(90).then(() => {
135                let packOpts: image.PackingOption = { format: "image/jpeg", quality: 98 };
136                try {
137                  // Call saveEditedContentWithImage to save the image.
138                  (this.getUIContext().getHostContext() as common.PhotoEditorExtensionContext).saveEditedContentWithImage(this.originalImage as image.PixelMap,
139                    packOpts).then(data => {
140                    if (data.resultCode == 0) {
141                      hilog.info(0x0000, TAG, `Save succeed.`);
142                    }
143                    hilog.info(0x0000, TAG,
144                        `saveContentEditingWithImage result: ${JSON.stringify(data)}`);
145                    this.newWant = data.want;
146                    // data.want.uri: URI of the edited image
147                    this.readImageByUri(this.newWant?.uri ?? "").then(imagePixMap => {
148                      this.editedImage = imagePixMap;
149                    })
150                  })
151                } catch (e) {
152                  hilog.error(0x0000, TAG, `saveContentEditingWithImage failed:${e}`);
153                  return;
154                }
155              })
156            }).margin({ top: 10 })
157
158            Button("terminateSelfWithResult").onClick((event => {
159              hilog.info(0x0000, TAG, `Finish the current editing.`);
160
161              let session = this.storage?.get('session') as UIExtensionContentSession;
162              // Terminate the ability and return the modification result to the caller.
163              session?.terminateSelfWithResult({ resultCode: 0, want: this.newWant });
164
165            })).margin({ top: 10 })
166
167            Image(this.originalImage).width("100%").height(200).margin({ top: 10 }).objectFit(ImageFit.Contain)
168
169            Image(this.editedImage).width("100%").height(200).margin({ top: 10 }).objectFit(ImageFit.Contain)
170          }
171          .width('100%')
172        }
173        .height('100%')
174        .backgroundColor(Color.Pink)
175        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
176      }
177    }
178
179    ```
1804. Register the PhotoEditorExtensionAbility in the **module.json5** file corresponding to the module.
181
182    Set **type** to **photoEditor** and **srcEntry** to the code path of the PhotoEditorExtensionAbility.
183
184    ```json
185    {
186      "module": {
187        "extensionAbilities": [
188          {
189            "name": "ExamplePhotoEditorAbility",
190            "icon": "$media:icon",
191            "description": "ExamplePhotoEditorAbility",
192            "type": "photoEditor",
193            "exported": true,
194            "srcEntry": "./ets/PhotoEditorExtensionAbility/ExamplePhotoEditorAbility.ets",
195            "label": "$string:EntryAbility_label",
196            "extensionProcessMode": "bundle"
197          },
198        ]
199      }
200    }
201    ```
202## Caller Application: Starting an Image Editing Application to Edit an Image
203On the UIAbility or UIExtensionAbility page, you can use **startAbilityByType** to start the vertical domain panel of image editing applications. The system automatically searches for and displays the image editing applications that have implemented the [PhotoEditorExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-photoEditorExtensionAbility.md) on the panel. Then the user can choose an application to edit the image, and the editing result is returned to the caller. The procedure is as follows:
2041. Import the modules.
205    ```ts
206    import { common, wantConstant } from '@kit.AbilityKit';
207    import { fileUri, picker } from '@kit.CoreFileKit';
208    ```
2092. (Optional) Select an image from Gallery.
210    ```ts
211    async photoPickerGetUri(): Promise < string > {
212      try {
213        let PhotoSelectOptions = new picker.PhotoSelectOptions();
214        PhotoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
215        PhotoSelectOptions.maxSelectNumber = 1;
216        let photoPicker = new picker.PhotoViewPicker();
217        let photoSelectResult: picker.PhotoSelectResult = await photoPicker.select(PhotoSelectOptions);
218        return photoSelectResult.photoUris[0];
219      } catch(error) {
220        let err: BusinessError = error as BusinessError;
221        hilog.error(0x0000, TAG, 'PhotoViewPicker failed with err: ' + JSON.stringify(err));
222      }
223      return "";
224    }
225    ```
2263. Copy the image to the local sandbox path.
227   ```ts
228    let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
229    let file: fileIo.File | undefined;
230    try {
231      file = fileIo.openSync(uri, fileIo.OpenMode.READ_ONLY);
232      hilog.info(0x0000, TAG, "file: " + file.fd);
233
234      let timeStamp = Date.now();
235      // Copy the image to the application sandbox path.
236      fileIo.copyFileSync(file.fd, context.filesDir + `/original-${timeStamp}.jpg`);
237
238      this.filePath = context.filesDir + `/original-${timeStamp}.jpg`;
239      this.originalImage = fileUri.getUriFromPath(this.filePath);
240    } catch (e) {
241      hilog.error(0x0000, TAG, `readImage failed:${e}`);
242    } finally {
243      fileIo.close(file);
244    }
245   ```
2464. In the callback function of **startAbilityByType**, use **want.uri** to obtain the URI of the edited image and perform corresponding processing.
247    ```ts
248      let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
249      let abilityStartCallback: common.AbilityStartCallback = {
250        onError: (code, name, message) => {
251          const tip: string = `code:` + code + ` name:` + name + ` message:` + message;
252          hilog.error(0x0000, TAG, "startAbilityByType:", tip);
253        },
254        onResult: (result) => {
255          // Obtain the URI of the edited image in the callback result and perform corresponding processing.
256          let uri = result.want?.uri ?? "";
257          hilog.info(0x0000, TAG, "PhotoEditorCaller result: " + JSON.stringify(result));
258          this.readImage(uri).then(imagePixMap => {
259            this.editedImage = imagePixMap;
260          });
261        }
262      }
263    ```
2645. Convert the image to an image URI and call **startAbilityByType** to start the image editing application panel.
265   ```ts
266    let uri = fileUri.getUriFromPath(this.filePath);
267    context.startAbilityByType("photoEditor", {
268      "ability.params.stream": [uri], // URI of the original image. Only one URI can be passed in.
269      "ability.want.params.uriPermissionFlag": wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION // At least the read permission should be shared to the image editing application panel.
270    } as Record<string, Object>, abilityStartCallback, (err) => {
271      let tip: string;
272      if (err) {
273        tip = `Start error: ${JSON.stringify(err)}`;
274        hilog.error(0x0000, TAG, `startAbilityByType: fail, err: ${JSON.stringify(err)}`);
275      } else {
276        tip = `Start success`;
277        hilog.info(0x0000, TAG, "startAbilityByType: ", `success`);
278      }
279    });
280   ```
281
282Example
283```ts
284import { common, wantConstant } from '@kit.AbilityKit';
285import { fileUri, picker } from '@kit.CoreFileKit';
286import { hilog } from '@kit.PerformanceAnalysisKit';
287import { fileIo } from '@kit.CoreFileKit';
288import { image } from '@kit.ImageKit';
289import { BusinessError } from '@kit.BasicServicesKit';
290import { JSON } from '@kit.ArkTS';
291import { photoAccessHelper } from '@kit.MediaLibraryKit';
292
293const TAG = 'PhotoEditorCaller';
294
295@Entry
296@Component
297struct Index {
298  @State message: string = 'selectImg';
299  @State originalImage: ResourceStr = "";
300  @State editedImage: PixelMap | null = null;
301  private filePath: string = "";
302
303  // Read the image based on the URI.
304  async readImage(uri: string): Promise < PixelMap | null > {
305    hilog.info(0x0000, TAG, "image uri: " + uri);
306    let file: fileIo.File | undefined;
307    try {
308      file = await fileIo.open(uri, fileIo.OpenMode.READ_ONLY);
309      hilog.info(0x0000, TAG, "file: " + file.fd);
310
311      let imageSourceApi: image.ImageSource = image.createImageSource(file.fd);
312      if(!imageSourceApi) {
313        hilog.info(0x0000, TAG, "imageSourceApi failed");
314        return null;
315      }
316      let pixmap: image.PixelMap = await imageSourceApi.createPixelMap();
317      if(!pixmap) {
318        hilog.info(0x0000, TAG, "createPixelMap failed");
319        return null;
320      }
321      this.editedImage = pixmap;
322      return pixmap;
323    } catch(e) {
324      hilog.error(0x0000, TAG, `readImage failed:${e}`);
325    } finally {
326      fileIo.close(file);
327    }
328    return null;
329  }
330
331  // Select an image from Gallery.
332  async photoPickerGetUri(): Promise<string> {
333	try {
334		let textInfo: photoAccessHelper.TextContextInfo = {
335			text: 'photo'
336		}
337		let recommendOptions: photoAccessHelper.RecommendationOptions = {
338			textContextInfo: textInfo
339		}
340		let options: photoAccessHelper.PhotoSelectOptions = {
341			MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
342			maxSelectNumber: 1,
343			recommendationOptions: recommendOptions
344		}
345		let photoPicker = new photoAccessHelper.PhotoViewPicker();
346		let photoSelectResult: photoAccessHelper.PhotoSelectResult = await photoPicker.select(options);
347		return photoSelectResult.photoUris[0];
348	} catch (error) {
349		let err: BusinessError = error as BusinessError;
350		hilog.error(0x0000, TAG, 'PhotoViewPicker failed with err: ' + JSON.stringify(err));
351	}
352	return "";
353  }
354
355  build() {
356    Row() {
357      Column() {
358        Text(this.message)
359          .fontSize(50)
360          .fontWeight(FontWeight.Bold)
361
362        Button("selectImg").onClick(event => {
363          // Select an image from Gallery.
364          this.photoPickerGetUri().then(uri => {
365            hilog.info(0x0000, TAG, "uri: " + uri);
366
367            let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
368            let file: fileIo.File | undefined;
369            try {
370              file = fileIo.openSync(uri, fileIo.OpenMode.READ_ONLY);
371              hilog.info(0x0000, TAG, "file: " + file.fd);
372
373              let timeStamp = Date.now();
374              // Copy the image to the application sandbox path.
375              fileIo.copyFileSync(file.fd, context.filesDir + `/original-${timeStamp}.jpg`);
376
377              this.filePath = context.filesDir + `/original-${timeStamp}.jpg`;
378              this.originalImage = fileUri.getUriFromPath(this.filePath);
379            } catch (e) {
380              hilog.info(0x0000, TAG, `readImage failed:${e}`);
381            } finally {
382              fileIo.close(file);
383            }
384          })
385
386        }).width('200').margin({ top: 20 })
387
388        Button("editImg").onClick(event => {
389          let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
390          let abilityStartCallback: common.AbilityStartCallback = {
391            onError: (code, name, message) => {
392              const tip: string = `code:` + code + ` name:` + name + ` message:` + message;
393              hilog.error(0x0000, TAG, "startAbilityByType:", tip);
394            },
395            onResult: (result) => {
396              // Obtain the URI of the edited image in the callback result and perform corresponding processing.
397              let uri = result.want?.uri ?? "";
398              hilog.info(0x0000, TAG, "PhotoEditorCaller result: " + JSON.stringify(result));
399              this.readImage(uri).then(imagePixMap => {
400                this.editedImage = imagePixMap;
401              });
402            }
403          }
404          // Convert the image to an image URI and call startAbilityByType to start the image editing application panel.
405          let uri = fileUri.getUriFromPath(this.filePath);
406          context.startAbilityByType("photoEditor", {
407            "ability.params.stream": [uri], // URI of the original image. Only one URI can be passed in.
408            "ability.want.params.uriPermissionFlag": wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION // At least the read permission should be shared to the image editing application panel.
409          } as Record<string, Object>, abilityStartCallback, (err) => {
410            let tip: string;
411            if (err) {
412              tip = `Start error: ${JSON.stringify(err)}`;
413              hilog.error(0x0000, TAG, `startAbilityByType: fail, err: ${JSON.stringify(err)}`);
414            } else {
415              tip = `Start success`;
416              hilog.info(0x0000, TAG, "startAbilityByType: ", `success`);
417            }
418          });
419
420        }).width('200').margin({ top: 20 })
421
422        Image(this.originalImage).width("100%").height(200).margin({ top: 20 }).objectFit(ImageFit.Contain)
423
424        Image(this.editedImage).width("100%").height(200).margin({ top: 20 }).objectFit(ImageFit.Contain)
425      }
426      .width('100%')
427    }
428    .height('100%')
429    .backgroundColor(Color.Orange)
430    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
431  }
432}
433
434```
435