• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 拉起图片编辑类应用(startAbilityByType)
2## 使用场景
3当应用自身不具备图片编辑能力、但存在图片编辑的场景时,可以通过startAbilityByType拉起图片编辑类应用扩展面板,由对应的应用完成图片编辑操作。图片编辑类应用可以通过PhotoEditorExtensionAbility实现图片编辑页面,并将该页面注册到图片编辑面板,从而将图片编辑能力开放给其他应用。
4
5流程示意图如下:
6
7![](figures/photoEditorExtensionAbility.png)
8
9例如:用户在图库App中选择编辑图片时,图库App可以通过startAbilityByType拉起图片编辑类应用扩展面板。用户可以从已实现PhotoEditorExtensionAbility应用中选择一款,并进行图片编辑。
10
11## 接口说明
12
13接口详情参见[PhotoEditorExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-photoEditorExtensionAbility.md)和[PhotoEditorExtensionContext](../reference/apis-ability-kit/js-apis-app-ability-photoEditorExtensionContext.md)。
14
15| **接口名**  | **描述** |
16| -------- | -------- |
17| onStartContentEditing(uri: string, want:Want, session: UIExtensionContentSession):void       | 可以执行读取原始图片、加载页面等操作。|
18| saveEditedContentWithImage(pixelMap: image.PixelMap, option: image.PackingOption): Promise\<AbilityResult\>  | 传入编辑过的图片的PixelMap对象并保存。   |
19
20## 图片编辑类应用实现图片编辑页面
21
221. 在DevEco Studio工程中手动新建一个PhotoEditorExtensionAbility。
23    1. 在工程Module对应的ets目录下,右键选择“New > Directory”,新建一个目录,如PhotoEditorExtensionAbility。
24    2. 在PhotoEditorExtensionAbility目录中,右键选择“New > File”,新建一个.ets文件,如ExamplePhotoEditorAbility.ets252. 在ExamplePhotoEditorAbility.ets中重写onCreate、onForeground、onBackground、onDestroy和onStartContentEditing的生命周期回调。
26
27    其中,需要在onStartContentEditing中加载入口页面文件pages/Index.ets,并将session、uri、实例对象等保存在LocalStorage中传递给页面。
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      // 获取图片,加载页面并将需要的参数传递给页面
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. 在page中实现图片编辑功能。
66
67    图片编辑完成后调用saveEditedContentWithImage保存图片,并将回调结果通过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      // 根据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              // 编辑图片功能实现
135              this.originalImage?.rotate(90).then(() => {
136                let packOpts: image.PackingOption = { format: "image/jpeg", quality: 98 };
137                try {
138                  // 调用saveEditedContentWithImage保存图片
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
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              // 关闭并回传修改结果给调用方
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. 在工程Module对应的module.json5配置文件中注册PhotoEditorExtensionAbility。
182
183    type标签需要配置为"photoEditor",srcEntry需要配置为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## 调用方拉起图片编辑类应用编辑图片
204开发者可以在UIAbility或者UIExtensionAbility的页面中通过接口startAbilityByType拉起图片编辑类应用扩展面板,系统将自动查找并在面板上展示基于[PhotoEditorExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-photoEditorExtensionAbility.md)实现的图片编辑应用,由用户选择某个应用来完成图片编辑的功能,最终将编辑的结果返回给到调用方,具体步骤如下:
2051. 导入模块。
206    ```ts
207    import { common, wantConstant } from '@kit.AbilityKit';
208    import { fileUri, picker } from '@kit.CoreFileKit';
209    ```
2102. (可选)实现从图库中选取图片。
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. 将图片拷贝到本地沙箱路径。
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      // 将用户图片拷贝到应用沙箱路径
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. 在startAbilityByType回调函数中,通过want.uri获取编辑后的图片uri,并做对应的处理。
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          // 获取到回调结果中编辑后的图片uri并做对应的处理
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. 将图片转换为图片uri,并调用startAbilityByType拉起图片编辑应用面板。
267   ```ts
268    let uri = fileUri.getUriFromPath(this.filePath);
269    context.startAbilityByType("photoEditor", {
270      "ability.params.stream": [uri], // 原始图片的uri,只支持传入一个uri
271      "ability.want.params.uriPermissionFlag": wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION // 至少需要分享读权限给到图片编辑面板
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
284示例:
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  // 根据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  // 图库中选取图片
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          // 图库中选取图片
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              // 将用户图片拷贝到应用沙箱路径
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              // 获取到回调结果中编辑后的图片uri并做对应的处理
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          // 将图片转换为图片uri,并调用startAbilityByType拉起图片编辑应用面板
402          let uri = fileUri.getUriFromPath(this.filePath);
403          context.startAbilityByType("photoEditor", {
404            "ability.params.stream": [uri], // 原始图片的uri,只支持传入一个uri
405            "ability.want.params.uriPermissionFlag": wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION // 至少需要分享读权限给到图片编辑面板
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```