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 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