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(pixelMap: 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 storage = LocalStorage.getShared()
77    const TAG = '[ExamplePhotoEditorAbility]';
78
79    @Entry
80    @Component
81    struct Index {
82      @State message: string = 'editImg';
83      @State originalImage: PixelMap | null = null;
84      @State editedImage: PixelMap | null = null;
85      private newWant ?: Want;
86
87      aboutToAppear(): void {
88        let originalImageUri = 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.info(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                  (getContext(this) 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 = 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.info(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 = getContext(this) 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.info(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 = getContext(this) 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';
291
292const TAG = 'PhotoEditorCaller';
293
294@Entry
295@Component
296struct Index {
297  @State message: string = 'selectImg';
298  @State originalImage: ResourceStr = "";
299  @State editedImage: PixelMap | null = null;
300  private filePath: string = "";
301
302  // Read the image based on the URI.
303  async readImage(uri: string): Promise < PixelMap | null > {
304    hilog.info(0x0000, TAG, "image uri: " + uri);
305    let file: fileIo.File | undefined;
306    try {
307      file = await fileIo.open(uri, fileIo.OpenMode.READ_ONLY);
308      hilog.info(0x0000, TAG, "file: " + file.fd);
309
310      let imageSourceApi: image.ImageSource = image.createImageSource(file.fd);
311      if(!imageSourceApi) {
312        hilog.info(0x0000, TAG, "imageSourceApi failed");
313        return null;
314      }
315      let pixmap: image.PixelMap = await imageSourceApi.createPixelMap();
316      if(!pixmap) {
317        hilog.info(0x0000, TAG, "createPixelMap failed");
318        return null;
319      }
320      this.editedImage = pixmap;
321      return pixmap;
322    } catch(e) {
323      hilog.info(0x0000, TAG, `readImage failed:${e}`);
324    } finally {
325      fileIo.close(file);
326    }
327    return null;
328  }
329
330  // Select an image from Gallery.
331  async photoPickerGetUri(): Promise < string > {
332    try {
333      let PhotoSelectOptions = new picker.PhotoSelectOptions();
334      PhotoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
335      PhotoSelectOptions.maxSelectNumber = 1;
336      let photoPicker = new picker.PhotoViewPicker();
337      let photoSelectResult: picker.PhotoSelectResult = await photoPicker.select(PhotoSelectOptions);
338      hilog.info(0x0000, TAG,
339        'PhotoViewPicker.select successfully, PhotoSelectResult uri: ' + JSON.stringify(photoSelectResult));
340      return photoSelectResult.photoUris[0];
341    } catch(error) {
342      let err: BusinessError = error as BusinessError;
343      hilog.info(0x0000, TAG, 'PhotoViewPicker failed with err: ' + JSON.stringify(err));
344    }
345    return "";
346  }
347
348  build() {
349    Row() {
350      Column() {
351        Text(this.message)
352          .fontSize(50)
353          .fontWeight(FontWeight.Bold)
354
355        Button("selectImg").onClick(event => {
356          // Select an image from Gallery.
357          this.photoPickerGetUri().then(uri => {
358            hilog.info(0x0000, TAG, "uri: " + uri);
359
360            let context = getContext(this) as common.UIAbilityContext;
361            let file: fileIo.File | undefined;
362            try {
363              file = fileIo.openSync(uri, fileIo.OpenMode.READ_ONLY);
364              hilog.info(0x0000, TAG, "file: " + file.fd);
365
366              let timeStamp = Date.now();
367              // Copy the image to the application sandbox path.
368              fileIo.copyFileSync(file.fd, context.filesDir + `/original-${timeStamp}.jpg`);
369
370              this.filePath = context.filesDir + `/original-${timeStamp}.jpg`;
371              this.originalImage = fileUri.getUriFromPath(this.filePath);
372            } catch (e) {
373              hilog.info(0x0000, TAG, `readImage failed:${e}`);
374            } finally {
375              fileIo.close(file);
376            }
377          })
378
379        }).width('200').margin({ top: 20 })
380
381        Button("editImg").onClick(event => {
382          let context = getContext(this) as common.UIAbilityContext;
383          let abilityStartCallback: common.AbilityStartCallback = {
384            onError: (code, name, message) => {
385              const tip: string = `code:` + code + ` name:` + name + ` message:` + message;
386              hilog.error(0x0000, TAG, "startAbilityByType:", tip);
387            },
388            onResult: (result) => {
389              // Obtain the URI of the edited image in the callback result and perform corresponding processing.
390              let uri = result.want?.uri ?? "";
391              hilog.info(0x0000, TAG, "PhotoEditorCaller result: " + JSON.stringify(result));
392              this.readImage(uri).then(imagePixMap => {
393                this.editedImage = imagePixMap;
394              });
395            }
396          }
397          // Convert the image to an image URI and call startAbilityByType to start the image editing application panel.
398          let uri = fileUri.getUriFromPath(this.filePath);
399          context.startAbilityByType("photoEditor", {
400            "ability.params.stream": [uri], // URI of the original image. Only one URI can be passed in.
401            "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.
402          } as Record<string, Object>, abilityStartCallback, (err) => {
403            let tip: string;
404            if (err) {
405              tip = `Start error: ${JSON.stringify(err)}`;
406              hilog.error(0x0000, TAG, `startAbilityByType: fail, err: ${JSON.stringify(err)}`);
407            } else {
408              tip = `Start success`;
409              hilog.info(0x0000, TAG, "startAbilityByType: ", `success`);
410            }
411          });
412
413        }).width('200').margin({ top: 20 })
414
415        Image(this.originalImage).width("100%").height(200).margin({ top: 20 }).objectFit(ImageFit.Contain)
416
417        Image(this.editedImage).width("100%").height(200).margin({ top: 20 }).objectFit(ImageFit.Contain)
418      }
419      .width('100%')
420    }
421    .height('100%')
422    .backgroundColor(Color.Orange)
423    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
424  }
425}
426
427```
428