• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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   ![](figures/mode-switching.gif)
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   ![](figures/front-rear-switching.gif)
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  ![](figures/flash-black.gif)
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