1# Starting 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          fileIo.closeSync(file);
116          return pixmap;
117        } catch(e) {
118          hilog.info(0x0000, TAG, `ReadImage failed:${e}`);
119        } finally {
120          fileIo.close(file);
121        }
122        return null;
123      }
124
125      build() {
126        Row() {
127          Column() {
128            Text(this.message)
129              .fontSize(50)
130              .fontWeight(FontWeight.Bold)
131
132            Button("RotateAndSaveImg").onClick(event => {
133              hilog.info(0x0000, TAG, `Start to edit image and save.`);
134              // Implement image editing.
135              this.originalImage?.rotate(90).then(() => {
136                let packOpts: image.PackingOption = { format: "image/jpeg", quality: 98 };
137                try {
138                  // Call saveEditedContentWithImage to save the image.
139                  (getContext(this) as common.PhotoEditorExtensionContext).saveEditedContentWithImage(this.originalImage as image.PixelMap,
140                    packOpts).then(data => {
141                    if (data.resultCode == 0) {
142                      hilog.info(0x0000, TAG, `Save succeed.`);
143                    }
144                    hilog.info(0x0000, TAG,
145                        `saveContentEditingWithImage result: ${JSON.stringify(data)}`);
146                    this.newWant = data.want;
147                    // data.want.uri: URI of the edited image
148                    this.readImageByUri(this.newWant?.uri ?? "").then(imagePixMap => {
149                      this.editedImage = imagePixMap;
150                    })
151                  })
152                } catch (e) {
153                  hilog.error(0x0000, TAG, `saveContentEditingWithImage failed:${e}`);
154                  return;
155                }
156              })
157            }).margin({ top: 10 })
158
159            Button("terminateSelfWithResult").onClick((event => {
160              hilog.info(0x0000, TAG, `Finish the current editing.`);
161
162              let session = storage.get('session') as UIExtensionContentSession;
163              // Terminate the ability and return the modification result to the caller.
164              session.terminateSelfWithResult({ resultCode: 0, want: this.newWant });
165
166            })).margin({ top: 10 })
167
168            Image(this.originalImage).width("100%").height(200).margin({ top: 10 }).objectFit(ImageFit.Contain)
169
170            Image(this.editedImage).width("100%").height(200).margin({ top: 10 }).objectFit(ImageFit.Contain)
171          }
172          .width('100%')
173        }
174        .height('100%')
175        .backgroundColor(Color.Pink)
176        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
177      }
178    }
179
180    ```
1814. Register the PhotoEditorExtensionAbility in the **module.json5** file corresponding to the module.
182
183    Set **type** to **photoEditor** and **srcEntry** to the code path of the PhotoEditorExtensionAbility.
184
185    ```json
186    {
187      "module": {
188        "extensionAbilities": [
189          {
190            "name": "ExamplePhotoEditorAbility",
191            "icon": "$media:icon",
192            "description": "ExamplePhotoEditorAbility",
193            "type": "photoEditor",
194            "exported": true,
195            "srcEntry": "./ets/PhotoEditorExtensionAbility/ExamplePhotoEditorAbility.ets",
196            "label": "$string:EntryAbility_label",
197            "extensionProcessMode": "bundle"
198          },
199        ]
200      }
201    }
202    ```
203## Caller Application: Starting an Image Editing Application to Edit an Image
204On 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:
2051. Import the modules.
206    ```ts
207    import { common, wantConstant } from '@kit.AbilityKit';
208    import { fileUri, picker } from '@kit.CoreFileKit';
209    ```
2102. (Optional) Select an image from Gallery.
211    ```ts
212    async photoPickerGetUri(): Promise < string > {
213      try {
214        let PhotoSelectOptions = new picker.PhotoSelectOptions();
215        PhotoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
216        PhotoSelectOptions.maxSelectNumber = 1;
217        let photoPicker = new picker.PhotoViewPicker();
218        let photoSelectResult: picker.PhotoSelectResult = await photoPicker.select(PhotoSelectOptions);
219        return photoSelectResult.photoUris[0];
220      } catch(error) {
221        let err: BusinessError = error as BusinessError;
222        hilog.info(0x0000, TAG, 'PhotoViewPicker failed with err: ' + JSON.stringify(err));
223      }
224      return "";
225    }
226    ```
2273. Copy the image to the local sandbox path.
228   ```ts
229    let context = getContext(this) as common.UIAbilityContext;
230    let file: fileIo.File | undefined;
231    try {
232      file = fileIo.openSync(uri, fileIo.OpenMode.READ_ONLY);
233      hilog.info(0x0000, TAG, "file: " + file.fd);
234
235      let timeStamp = Date.now();
236      // Copy the image to the application sandbox path.
237      fileIo.copyFileSync(file.fd, context.filesDir + `/original-${timeStamp}.jpg`);
238      fileIo.closeSync(file);
239
240      this.filePath = context.filesDir + `/original-${timeStamp}.jpg`;
241      this.originalImage = fileUri.getUriFromPath(this.filePath);
242    } catch (e) {
243      hilog.info(0x0000, TAG, `readImage failed:${e}`);
244    } finally {
245      fileIo.close(file);
246    }
247   ```
2484. In the callback function of **startAbilityByType**, use **want.uri** to obtain the URI of the edited image and perform corresponding processing.
249    ```ts
250      let context = getContext(this) as common.UIAbilityContext;
251      let abilityStartCallback: common.AbilityStartCallback = {
252        onError: (code, name, message) => {
253          const tip: string = `code:` + code + ` name:` + name + ` message:` + message;
254          hilog.error(0x0000, TAG, "startAbilityByType:", tip);
255        },
256        onResult: (result) => {
257          // Obtain the URI of the edited image in the callback result and perform corresponding processing.
258          let uri = result.want?.uri ?? "";
259          hilog.info(0x0000, TAG, "PhotoEditorCaller result: " + JSON.stringify(result));
260          this.readImage(uri).then(imagePixMap => {
261            this.editedImage = imagePixMap;
262          });
263        }
264      }
265    ```
2665. Convert the image to an image URI and call **startAbilityByType** to start the image editing application panel.
267   ```ts
268    let uri = fileUri.getUriFromPath(this.filePath);
269    context.startAbilityByType("photoEditor", {
270      "ability.params.stream": [uri], // URI of the original image. Only one URI can be passed in.
271      "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.
272    } as Record<string, Object>, abilityStartCallback, (err) => {
273      let tip: string;
274      if (err) {
275        tip = `Start error: ${JSON.stringify(err)}`;
276        hilog.error(0x0000, TAG, `startAbilityByType: fail, err: ${JSON.stringify(err)}`);
277      } else {
278        tip = `Start success`;
279        hilog.info(0x0000, TAG, "startAbilityByType: ", `success`);
280      }
281    });
282   ```
283
284Example 
285```ts
286import { common, wantConstant } from '@kit.AbilityKit';
287import { fileUri, picker } from '@kit.CoreFileKit';
288import { hilog } from '@kit.PerformanceAnalysisKit';
289import { fileIo } from '@kit.CoreFileKit';
290import { image } from '@kit.ImageKit';
291import { BusinessError } from '@kit.BasicServicesKit';
292import { JSON } from '@kit.ArkTS';
293
294const TAG = 'PhotoEditorCaller';
295
296@Entry
297@Component
298struct Index {
299  @State message: string = 'selectImg';
300  @State originalImage: ResourceStr = "";
301  @State editedImage: PixelMap | null = null;
302  private filePath: string = "";
303
304  // Read the image based on the URI.
305  async readImage(uri: string): Promise < PixelMap | null > {
306    hilog.info(0x0000, TAG, "image uri: " + uri);
307    let file: fileIo.File | undefined;
308    try {
309      file = await fileIo.open(uri, fileIo.OpenMode.READ_ONLY);
310      hilog.info(0x0000, TAG, "file: " + file.fd);
311
312      let imageSourceApi: image.ImageSource = image.createImageSource(file.fd);
313      if(!imageSourceApi) {
314        hilog.info(0x0000, TAG, "imageSourceApi failed");
315        return null;
316      }
317      let pixmap: image.PixelMap = await imageSourceApi.createPixelMap();
318      if(!pixmap) {
319        hilog.info(0x0000, TAG, "createPixelMap failed");
320        return null;
321      }
322      this.editedImage = pixmap;
323      fileIo.closeSync(file);
324      return pixmap;
325    } catch(e) {
326      hilog.info(0x0000, TAG, `readImage failed:${e}`);
327    } finally {
328      fileIo.close(file);
329    }
330    return null;
331  }
332
333  // Select an image from Gallery.
334  async photoPickerGetUri(): Promise < string > {
335    try {
336      let PhotoSelectOptions = new picker.PhotoSelectOptions();
337      PhotoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
338      PhotoSelectOptions.maxSelectNumber = 1;
339      let photoPicker = new picker.PhotoViewPicker();
340      let photoSelectResult: picker.PhotoSelectResult = await photoPicker.select(PhotoSelectOptions);
341      hilog.info(0x0000, TAG,
342        'PhotoViewPicker.select successfully, PhotoSelectResult uri: ' + JSON.stringify(photoSelectResult));
343      return photoSelectResult.photoUris[0];
344    } catch(error) {
345      let err: BusinessError = error as BusinessError;
346      hilog.info(0x0000, TAG, 'PhotoViewPicker failed with err: ' + JSON.stringify(err));
347    }
348    return "";
349  }
350
351  build() {
352    Row() {
353      Column() {
354        Text(this.message)
355          .fontSize(50)
356          .fontWeight(FontWeight.Bold)
357
358        Button("selectImg").onClick(event => {
359          // Select an image from Gallery.
360          this.photoPickerGetUri().then(uri => {
361            hilog.info(0x0000, TAG, "uri: " + uri);
362
363            let context = getContext(this) as common.UIAbilityContext;
364            let file: fileIo.File | undefined;
365            try {
366              file = fileIo.openSync(uri, fileIo.OpenMode.READ_ONLY);
367              hilog.info(0x0000, TAG, "file: " + file.fd);
368
369              let timeStamp = Date.now();
370              // Copy the image to the application sandbox path.
371              fileIo.copyFileSync(file.fd, context.filesDir + `/original-${timeStamp}.jpg`);
372              fileIo.closeSync(file);
373
374              this.filePath = context.filesDir + `/original-${timeStamp}.jpg`;
375              this.originalImage = fileUri.getUriFromPath(this.filePath);
376            } catch (e) {
377              hilog.info(0x0000, TAG, `readImage failed:${e}`);
378            } finally {
379              fileIo.close(file);
380            }
381          })
382
383        }).width('200').margin({ top: 20 })
384
385        Button("editImg").onClick(event => {
386          let context = getContext(this) as common.UIAbilityContext;
387          let abilityStartCallback: common.AbilityStartCallback = {
388            onError: (code, name, message) => {
389              const tip: string = `code:` + code + ` name:` + name + ` message:` + message;
390              hilog.error(0x0000, TAG, "startAbilityByType:", tip);
391            },
392            onResult: (result) => {
393              // Obtain the URI of the edited image in the callback result and perform corresponding processing.
394              let uri = result.want?.uri ?? "";
395              hilog.info(0x0000, TAG, "PhotoEditorCaller result: " + JSON.stringify(result));
396              this.readImage(uri).then(imagePixMap => {
397                this.editedImage = imagePixMap;
398              });
399            }
400          }
401          // Convert the image to an image URI and call startAbilityByType to start the image editing application panel.
402          let uri = fileUri.getUriFromPath(this.filePath);
403          context.startAbilityByType("photoEditor", {
404            "ability.params.stream": [uri], // URI of the original image. Only one URI can be passed in.
405            "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.
406          } as Record<string, Object>, abilityStartCallback, (err) => {
407            let tip: string;
408            if (err) {
409              tip = `Start error: ${JSON.stringify(err)}`;
410              hilog.error(0x0000, TAG, `startAbilityByType: fail, err: ${JSON.stringify(err)}`);
411            } else {
412              tip = `Start success`;
413              hilog.info(0x0000, TAG, "startAbilityByType: ", `success`);
414            }
415          });
416
417        }).width('200').margin({ top: 20 })
418
419        Image(this.originalImage).width("100%").height(200).margin({ top: 20 }).objectFit(ImageFit.Contain)
420
421        Image(this.editedImage).width("100%").height(200).margin({ top: 20 }).objectFit(ImageFit.Contain)
422      }
423      .width('100%')
424    }
425    .height('100%')
426    .backgroundColor(Color.Orange)
427    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
428  }
429}
430
431```
432