• 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(pixeMap: 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 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      // 根据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              // 编辑图片功能实现
134              this.originalImage?.rotate(90).then(() => {
135                let packOpts: image.PackingOption = { format: "image/jpeg", quality: 98 };
136                try {
137                  // 调用saveEditedContentWithImage保存图片
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
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              // 关闭并回传修改结果给调用方
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. 在工程Module对应的module.json5配置文件中注册PhotoEditorExtensionAbility。
181
182    type标签需要配置为"photoEditor",srcEntry需要配置为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## 调用方拉起图片编辑类应用编辑图片
203开发者可以在UIAbility或者UIExtensionAbility的页面中通过接口startAbilityByType拉起图片编辑类应用扩展面板,系统将自动查找并在面板上展示基于[PhotoEditorExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-photoEditorExtensionAbility.md)实现的图片编辑应用,由用户选择某个应用来完成图片编辑的功能,最终将编辑的结果返回给到调用方,具体步骤如下:
2041. 导入模块。
205    ```ts
206    import { common, wantConstant } from '@kit.AbilityKit';
207    import { fileUri, picker } from '@kit.CoreFileKit';
208    ```
2092. (可选)实现从图库中选取图片。
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. 将图片拷贝到本地沙箱路径。
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      // 将用户图片拷贝到应用沙箱路径
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. 在startAbilityByType回调函数中,通过want.uri获取编辑后的图片uri,并做对应的处理。
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          // 获取到回调结果中编辑后的图片uri并做对应的处理
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. 将图片转换为图片uri,并调用startAbilityByType拉起图片编辑应用面板。
265   ```ts
266    let uri = fileUri.getUriFromPath(this.filePath);
267    context.startAbilityByType("photoEditor", {
268      "ability.params.stream": [uri], // 原始图片的uri,只支持传入一个uri
269      "ability.want.params.uriPermissionFlag": wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION // 至少需要分享读权限给到图片编辑面板
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
282示例:
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  // 根据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  // 图库中选取图片
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          // 图库中选取图片
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              // 将用户图片拷贝到应用沙箱路径
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              // 获取到回调结果中编辑后的图片uri并做对应的处理
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          // 将图片转换为图片uri,并调用startAbilityByType拉起图片编辑应用面板
405          let uri = fileUri.getUriFromPath(this.filePath);
406          context.startAbilityByType("photoEditor", {
407            "ability.params.stream": [uri], // 原始图片的uri,只支持传入一个uri
408            "ability.want.params.uriPermissionFlag": wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION // 至少需要分享读权限给到图片编辑面板
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```