1# Basic Camera Animation (ArkTS) 2<!--Kit: Camera Kit--> 3<!--Subsystem: Multimedia--> 4<!--Owner: @qano--> 5<!--SE: @leo_ysl--> 6<!--TSE: @xchaosioda--> 7 8When using the camera, transitions such as changing camera modes or switching between front and rear cameras will always involve replacing the preview stream. To enhance user experience, smooth animations can be effectively incorporated. This topic describes how to use preview stream snapshots and ArkUI's [explicit animations](../../reference/apis-arkui/arkui-ts/ts-explicit-animatetoimmediately.md) to implement three key scene transitions: 9 10- Mode switching: Use preview stream snapshots to create a blur effect for transition. 11 12 The following depicts the transition from video mode to photo mode. 13 14  15 16- Front/Rear camera switching: Use preview stream snapshots to create a blur-and-flip effect for transition. 17 18 The following demonstrates the transition from using the front camera to the rear camera. 19 20  21 22- Photo capture blackout: Use a blackout component to overlay the preview stream to create a blackout transition. 23 24 The following depicts the moment of photo capture. 25 26  27 28## Blackout Animation 29 30Use component overlay to implement the blackout effect. 31 32The sample code in the following steps is the internal method or logic of a custom component (component decorated by @Component). 33 341. Import dependencies. Specifically, import the camera, image, and ArkUI modules. 35 36 ```ts 37 import { curves } from '@kit.ArkUI'; 38 ``` 39 402. Build a blackout component. 41 42 Define a blackout component, which is displayed during camera blackouts for photo capture or front/rear camera switching. This component will block the XComponent. 43 44 Define the component's properties as follows: 45 46 ```ts 47 @State isShowBlack: boolean = false; // Whether to show the blackout component. 48 @StorageLink('captureClick') @Watch('onCaptureClick') captureClickFlag: number = 0; // Entry for the blackout animation. 49 @State flashBlackOpacity: number = 1; // Opacity of the blackout component. 50 ``` 51 52 Implement the logic of the blackout component as follows: 53 54 ```ts 55 // The component is displayed during camera blackouts for photo capture or camera switching and is used to block the XComponent. 56 if (this.isShowBlack) { 57 Column() 58 .key('black') 59 .width(this.getUIContext().px2vp(1080)) // The width and height must be the same as those of the XComponent of the preview stream. The layer is above the preview stream and below the snapshot component. 60 .height(this.getUIContext().px2vp(1920)) 61 .backgroundColor(Color.Black) 62 .opacity(this.flashBlackOpacity) 63 } 64 ``` 65 663. Implement the blackout effect. 67 68 ```ts 69 // Internal methods of the component decorated by @Component. 70 flashBlackAnim() { 71 console.info('flashBlackAnim E'); 72 this.flashBlackOpacity = 1; // The blackout component is opaque. 73 this.isShowBlack = true; // Show the blackout component. 74 animateToImmediately({ 75 curve: curves.interpolatingSpring(1, 1, 410, 38), 76 delay: 50, // A black screen is displayed after a delay of 50 ms. 77 onFinish: () => { 78 this.isShowBlack = false; // Hide the blackout component. 79 this.flashBlackOpacity = 1; 80 console.info('flashBlackAnim X'); 81 } 82 }, () => { 83 this.flashBlackOpacity = 0; // The blackout component is changed from opaque to transparent. 84 }) 85 } 86 ``` 87 884. Trigger the blackout effect. 89 90 When a user touches the **PHOTO** button, the value of **CaptureClick** bound to the StorageLink is updated, and **onCaptureClick** is invoked. In this case, the animation starts to play. 91 92 ```ts 93 onCaptureClick(): void { 94 console.info('onCaptureClick'); 95 this.flashBlackAnim(); 96 } 97 ``` 98 99 100 101## Blur Animation 102 103You can use preview stream snapshots to create a blur effect for mode switching or front/rear camera switching. 104 105The sample code in the following steps (except step 2) is the internal method or logic of a custom component (component decorated by @Component). 106 1071. Import dependencies. Specifically, import the camera, image, and ArkUI modules. 108 109 ```ts 110 import { camera } from '@kit.CameraKit'; 111 import { image } from '@kit.ImageKit'; 112 import { curves } from '@kit.ArkUI'; 113 ``` 114 1152. Obtain a preview stream snapshot. 116 117 Preview stream snapshots are obtained by calling [image.createPixelMapFromSurface](../../reference/apis-image-kit/arkts-apis-image-f.md#imagecreatepixelmapfromsurface11) provided by the image module. In this API, **surfaceId** is the surface ID of the current preview stream, and **size** is the width and height of the current preview stream profile. Create a snapshot utility class (TS file), import the dependency, and export the snapshot retrieval API for the page to use. The code snippet below shows the implementation of the snapshot utility class: 118 119 ```ts 120 export class BlurAnimateUtil { 121 public static surfaceShot: image.PixelMap; 122 123 /** 124 * Obtain a surface snapshot. 125 * @param surfaceId 126 * @returns 127 */ 128 public static async doSurfaceShot(surfaceId: string) { 129 console.info(`doSurfaceShot surfaceId:${surfaceId}.`); 130 if (surfaceId === '') { 131 console.error('surface not ready!'); 132 return; 133 } 134 try { 135 if (BlurAnimateUtil.surfaceShot) { 136 await BlurAnimateUtil.surfaceShot.release(); 137 } 138 BlurAnimateUtil.surfaceShot = await image.createPixelMapFromSurface(surfaceId, { 139 size: { width: 1920, height: 1080 }, // Obtain the width and height of the preview stream profile. 140 x: 0, 141 y: 0 142 }); 143 let imageInfo: image.ImageInfo = await BlurAnimateUtil.surfaceShot.getImageInfo(); 144 console.info('doSurfaceShot surfaceShot:' + JSON.stringify(imageInfo.size)); 145 } catch (err) { 146 console.error(err); 147 } 148 } 149 150 /** 151 * Obtain the snapshot captured by calling doSurfaceShot. 152 * @returns 153 */ 154 public static getSurfaceShot(): image.PixelMap { 155 return BlurAnimateUtil.surfaceShot; 156 } 157 } 158 ``` 159 1603. Build a snapshot component. 161 162 Define a snapshot component, and place it above the XComponent of the preview stream to block the XComponent. 163 164 Define the component's properties as follows: 165 166 ```ts 167 @State isShowBlur: boolean = false; // Whether to show the snapshot component. 168 @StorageLink('modeChange') @Watch('onModeChange') modeChangeFlag: number = 0; // Entry for triggering the mode switching animation. 169 @StorageLink('switchCamera') @Watch('onSwitchCamera') switchCameraFlag: number = 0; // Entry for triggering the front/rear camera switching animation. 170 @StorageLink('frameStart') @Watch('onFrameStart') frameStartFlag: number = 0; // Entry for the fade-out animation. 171 @State screenshotPixelMap: image.PixelMap | undefined = undefined; // PixelMap of the snapshot component. 172 @State surfaceId: string ="; // Surface ID of the XComponent of the current preview stream. 173 @StorageLink('curPosition') curPosition: number = 0; // Current camera position (front or rear). 174 @State shotImgBlur: number = 0; // Blur degree of the snapshot component. 175 @State shotImgOpacity: number = 1; // Opacity of the snapshot component. 176 @State shotImgScale: ScaleOptions = {x: 1, y: 1}; // Scale ratio of the snapshot component. 177 @State shotImgRotation: RotateOptions = { y: 0.5, angle: 0 } // Rotation angle of the snapshot component. 178 ``` 179 180 Implement the snapshot component as follows: 181 182 ```ts 183 // Snapshot component is placed above the XComponent of the preview stream. 184 if (this.isShowBlur) { 185 Column() { 186 Image(this.screenshotPixelMap) 187 .blur(this.shotImgBlur) 188 .opacity(this.shotImgOpacity) 189 .rotate(this.shotImgRotation) // Provided by ArkUI for component rotation. 190 .scale(this.shotImgScale) 191 .width(this.getUIContext().px2vp(1080)) // The width and height must be the same as those of the XComponent of the preview stream. The layer is above the preview stream. 192 .height(this.getUIContext().px2vp(1920)) 193 .syncLoad(true) 194 } 195 .width(this.getUIContext().px2vp(1080)) 196 .height(this.getUIContext().px2vp(1920)) 197 } 198 ``` 199 2004. (Optional) Implement the fade-in blur effect. 201 202 The mode switching animation is implemented in two phases: fade-in blur animation and fade-out blur animation. 203 204 When a user touches a button, and the camera takes a snapshot of the preview stream. The snapshot component is displayed, which gradually blurs over the existing preview stream, implementing the fade-in blur animation. 205 206 > **NOTE** 207 > 208 > The **image.createPixelMapFromSurface** API, which is used to extract the surface content into a PixelMap, operates differently from the rendering logic of the XComponent. As such, different rotation compensations must be applied to both the image content and the component for the front and rear cameras. 209 210 ```ts 211 async showBlurAnim() { 212 console.info('showBlurAnim E'); 213 // Obtain the surface snapshot. 214 let shotPixel = BlurAnimateUtil.getSurfaceShot(); 215 // The rear camera is used. 216 if (this.curPosition === 0) { 217 console.info('showBlurAnim BACK'); 218 // For candy bar phones, a 90° rotation compensation is applied to the content for a snapshot taken with the rear camera. 219 await shotPixel.rotate(90); // Provided by Image Kit for image content rotation. 220 // For candy bar phones, a 0° rotation compensation is applied to the component for a snapshot taken with the rear camera. 221 this.shotImgRotation = { y: 0.5, angle: 0 }; 222 } else { 223 console.info('showBlurAnim FRONT'); 224 // For candy bar phones, a 270° rotation compensation is applied to the content for a snapshot taken with the front camera. 225 await shotPixel.rotate(270); 226 // For candy bar phones, a 180° rotation compensation is applied to the component for a snapshot taken with the front camera. 227 this.shotImgRotation = { y: 0.5, angle: 180 }; 228 } 229 this.screenshotPixelMap = shotPixel; 230 // Initialize animation parameters. 231 this.shotImgBlur = 0; // No blur. 232 this.shotImgOpacity = 1; // Opaque. 233 this.isShowBlur = true; // Show the snapshot component. 234 animateToImmediately( 235 { 236 duration: 200, 237 curve: Curve.Friction, 238 onFinish: async () => { 239 console.info('showBlurAnim X'); 240 } 241 }, 242 () => { 243 this.shotImgBlur = 48; // Blur intensity of the snapshot component. 244 } 245 ); 246 } 247 ``` 248 2495. Implement the fade-out blur animation. 250 251 The fade-out blur animation is triggered by the event [on('frameStart')](../../reference/apis-camera-kit/arkts-apis-camera-PreviewOutput.md#onframestart) of the new preview stream. During this effect, the snapshot component gradually becomes clear, revealing the new preview stream. 252 253 ```ts 254 hideBlurAnim(): void { 255 this.isShowBlack = false; 256 console.info('hideBlurAnim E'); 257 animateToImmediately({ 258 duration: 200, 259 curve: Curve.FastOutSlowIn, 260 onFinish: () => { 261 this.isShowBlur = false; // Hide the blur component. 262 this.shotImgBlur = 0; 263 this.shotImgOpacity = 1; 264 console.info('hideBlurAnim X'); 265 } 266 }, () => { 267 // Change the opacity of the snapshot component. 268 this.shotImgOpacity = 0; // Opacity change of the snapshot component. 269 }); 270 } 271 ``` 272 2736. (Optional) Implement the blur-and-flip animation. 274 275 The animation is carried out in two phases: blur-and-flip and fade-out blur, where fade-out blur is the same as that in step 5. 276 277 The blur-and-flip animation is realized through two stages of component rotation—initially a 90° rotation outwards, and then a 90° rotation inwards—accompanied by additional effects such as blur, opacity changes, and scaling. 278 279 To ensure that the preview stream is not exposed during flip, you must also build a blackout component to mask the XComponent, following the instructions provided in step 2 in [Blackout Animation](#blackout-animation). 280 281 ```ts 282 /** 283 * A 90° rotation outwards first for transition between the front and real cameras. 284 */ 285 async rotateFirstAnim() { 286 console.info('rotateFirstAnim E'); 287 // Obtain the surface snapshot. 288 let shotPixel = BlurAnimateUtil.getSurfaceShot(); 289 // Switch from the rear camera to the front camera. 290 if (this.curPosition === 1) { 291 console.info('rotateFirstAnim BACK'); 292 // For candy bar phones, a 90° rotation compensation is applied to the content for a snapshot when switching from the rear camera to the front camera. 293 await shotPixel.rotate(90); // Provided by Image Kit for image content rotation. 294 // For candy bar phones, a 0° rotation compensation is applied to the component for a snapshot when switching from the rear camera to the front camera. 295 this.shotImgRotation = { y: 0.5, angle: 0 }; 296 } else { 297 console.info('rotateFirstAnim FRONT'); 298 // For candy bar phones, a 270° rotation compensation is applied to the content for a snapshot when switching from the front camera to the rear camera. 299 await shotPixel.rotate(270); 300 // For candy bar phones, a 180° rotation compensation is applied to the component for a snapshot when switching from the front camera to the rear camera. 301 this.shotImgRotation = { y: 0.5, angle: 180 }; 302 } 303 this.screenshotPixelMap = shotPixel; 304 this.isShowBlack = true; // Show the blackout component to mask the preview stream. 305 this.isShowBlur = true; // Show the snapshot component. 306 animateToImmediately( 307 { 308 duration: 200, 309 delay: 50, // This delay ensures that the component's scaling and blur effects are triggered in prior to the flip effect. 310 curve: curves.cubicBezierCurve(0.20, 0.00, 0.83, 1.00), 311 onFinish: () => { 312 console.info('rotateFirstAnim X'); 313 // Trigger the second-phase flip effect after onFinish. 314 this.rotateSecondAnim(); 315 } 316 }, 317 () => { 318 // Flip outwards. 319 if (this.curPosition === 1) { 320 this.shotImgRotation = { y: 0.5, angle: 90 }; 321 } else { 322 this.shotImgRotation = { y: 0.5, angle: 270 }; 323 } 324 } 325 ) 326 } 327 328 /** 329 * Flip inwards by 90°. 330 */ 331 async rotateSecondAnim() { 332 console.info('rotateSecondAnim E'); 333 // Obtain the surface snapshot. 334 let shotPixel = BlurAnimateUtil.getSurfaceShot(); 335 // The rear camera is used. 336 if (this.curPosition === 1) { 337 // For candy bar phones, a 90° rotation compensation is applied to the content for a snapshot taken with the rear camera. 338 await shotPixel.rotate(90); 339 // A -90° rotation compensation is applied to the component to ensure that the image is not mirrored after the second-phase flip. 340 this.shotImgRotation = { y: 0.5, angle: 90 }; 341 } else { // The front camera is used. 342 // For candy bar phones, a 270° rotation compensation is applied to the content for a snapshot taken with the front camera. 343 await shotPixel.rotate(270); 344 // For candy bar phones, a 180° rotation compensation is applied to the component for a snapshot taken with the front camera. 345 this.shotImgRotation = { y: 0.5, angle: 180 }; 346 } 347 this.screenshotPixelMap = shotPixel; 348 animateToImmediately( 349 { 350 duration: 200, 351 curve: curves.cubicBezierCurve(0.17, 0.00, 0.20, 1.00), 352 onFinish: () => { 353 console.info('rotateSecondAnim X'); 354 } 355 }, 356 () => { 357 // Flip inwards to the initial state. 358 if (this.curPosition === 1) { 359 this.shotImgRotation = { y: 0.5, angle: 0 }; 360 } else { 361 this.shotImgRotation = { y: 0.5, angle: 180 }; 362 } 363 } 364 ) 365 } 366 367 /** 368 * Flip outwards by 90°. 369 */ 370 blurFirstAnim() { 371 console.info('blurFirstAnim E'); 372 // Initialize animation parameters. 373 this.shotImgBlur = 0; // No blur. 374 this.shotImgOpacity = 1; // Opaque. 375 this.shotImgScale = { x: 1, y: 1 }; 376 animateToImmediately( 377 { 378 duration: 200, 379 curve: Curve.Sharp, 380 onFinish: () => { 381 console.info('blurFirstAnim X'); 382 this.blurSecondAnim(); 383 } 384 }, 385 () => { 386 // Blur. 387 this.shotImgBlur = 48; 388 // Scale out. 389 this.shotImgScale = { x: 0.75, y: 0.75 }; 390 } 391 ); 392 } 393 394 /** 395 * Flip inwards by 90°. 396 */ 397 blurSecondAnim() { 398 console.info('blurSecondAnim E'); 399 animateToImmediately( 400 { 401 duration: 200, 402 curve: Curve.Sharp, 403 onFinish: () => { 404 console.info('blurSecondAnim X'); 405 } 406 }, 407 () => { 408 // Scale in to the initial size. 409 this.shotImgScale = { x: 1, y: 1 }; 410 } 411 ) 412 } 413 ``` 414 4157. Trigger the animations on demand. 416 417 For the mode switching animation, once a user touches the mode button, the **doSurfaceShot** API is invoked, the value of **modeChange** bound to the StorageLink is updated, and the **onModeChange** callback is triggered. In this case, the animation starts to play. 418 419 ```ts 420 onModeChange(): void { 421 console.info('onModeChange'); 422 this.showBlurAnim(); 423 } 424 ``` 425 426 For the front/rear camera switching animation, once a user touches the button to switch between the front and rear cameras, the **doSurfaceShot** API is invoked, the value of **switchCamera** bound to the StorageLink is updated, and the **onSwitchCamera** callback is triggered. In this case, the animation starts to play. 427 428 ```ts 429 onSwitchCamera(): void { 430 console.info('onSwitchCamera'); 431 this.blurFirstAnim(); 432 this.rotateFirstAnim(); 433 } 434 ``` 435 436 For the fade-out blur animation, you must listen for the event [on('frameStart')](../../reference/apis-camera-kit/arkts-apis-camera-PreviewOutput.md#onframestart) of the preview stream. Once the value of **frameStart** bound to the StorageLink is updated, and the **onFrameStart** callback is triggered, the animation starts. 437 438 ```ts 439 onFrameStart(): void { 440 console.info('onFrameStart'); 441 this.hideBlurAnim(); 442 } 443 ``` 444