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