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