• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Using Component Snapshot (ComponentSnapshot)
2## Overview
3Component snapshot is the capability to generate a pixel map ([PixelMap](../reference/apis-image-kit/arkts-apis-image-PixelMap.md)) from the rendering result of a component node tree within an application. It supports two approaches:
4
5- Taking a snapshot of a component that is already attached to the UI tree
6- Taking a snapshot of an offline component implemented using **Builder** or **ComponentContent**.
7
8> **NOTE**
9>
10> Component snapshot relies on UI context and must be called in an environment with a clear context. Therefore, preferably use the [ComponentSnapshot](../reference/apis-arkui/js-apis-arkui-UIContext.md#componentsnapshot12) object returned by the **getComponentSnapshot** API of **UIContext**. Avoid using the **componentSnapshot** API imported directly from @kit.ArkUI.
11
12
13### Taking a Snapshot of a Component Attached to the UI Tree
14To take a snapshot of a component that is already attached to the UI tree, use [get](../reference/apis-arkui/js-apis-arkui-UIContext.md#get12-1) or [getSync](../reference/apis-arkui/js-apis-arkui-UIContext.md#getsync12). Pass the component ID (configured in advance using the **id** universal attribute) to specify the component root node. The system only traverses components attached to the tree when searching for the component to take a snapshot; it does not search cached or off-screen components. The system uses the first found result, so the application must ensure the uniqueness of component IDs.
15
16If you know the ID of the component with [getUniqueId](../reference/apis-arkui/js-apis-arkui-frameNode.md#getuniqueid12), you can also use [getWithUniqueId](../reference/apis-arkui/js-apis-arkui-UIContext.md#getwithuniqueid15) or [getSyncWithUniqueId](../reference/apis-arkui/js-apis-arkui-UIContext.md#getsyncwithuniqueid15) to take a snapshot of the component directly, bypassing the component search process.
17
18The snapshot captures only the most recent frame. If you trigger a component update and immediately take a snapshot, the updated content will not be captured; the snapshot will return the previous frame's content.
19
20> **NOTE**
21>
22> Avoid triggering updates of the component being snapped to prevent interference with the snapshot content.
23
24
25### Taking a Snapshot of an Offline Component
26Offline components are components that are encapsulated using **Builder** or **ComponentContent** but have not yet been attached to the tree. To take snapshots of them, use [createFromBuilder](../reference/apis-arkui/js-apis-arkui-UIContext.md#createfrombuilder12-1) and [createFromComponent](../reference/apis-arkui/js-apis-arkui-UIContext.md#createfromcomponent18).
27
28Since offline components do not participate in actual rendering, taking snapshots of them takes longer because the system must first perform offline construction, layout, and resource loading. Snapshots taken before these operations complete may return unexpected results. Therefore, it is usually necessary to set a sufficient delay to ensure the system completes these operations. For image resources, set the [syncLoad](../reference/apis-arkui/arkui-ts/ts-basic-components-image.md#syncload8) attribute of the **Image** component to **true** to force synchronous loading. This ensures images are loaded, downloaded, and decoded during offline component construction, allowing the image pixels to be correctly displayed during the snapshot process.
29
30## Use Cases
31The following use cases illustrate common usage methods of the component snapshot capability.
32
33### Capturing Long Content (Scrolling Snapshot)
34Long content is usually implemented using scrollable container components. When you take a snapshot, only the visible content within the container is captured, and content beyond the boundary is not included. If **LazyForEach** or **Repeat** is used, content outside the display range is not built or captured by the system.
35
36You can use scrollable container APIs to simulate user swiping for page-by-page snapshots, then stitch the **PixelMap** objects of each page by offset to generate a complete long image. The key points are simulating swiping, maintaining the relationship between displacement and pixel maps, and implementing **PixelMap** read and write operations.
37
38**Step 1: Add a scroll controller and event listener.**
39
40To simulate scrolling and listen for the component's scroll offset, you need to add a scroll controller and scroll listener to the scrollable container (**List** component in this example).
41
42```ts
43// src/main/ets/view/ScrollSnapshot.ets
44@Component
45export struct ScrollSnapshot {
46  private scroller: Scroller = new Scroller();
47  private listComponentWidth: number = 0;
48  private listComponentHeight: number = 0;
49  // Current offset of the List component
50  private curYOffset: number = 0;
51  // Scroll distance per step
52  private scrollHeight: number = 0;
53
54
55  // ...
56  build() {
57    // ...
58    Stack() {
59      // ...
60      // 1.1 Bind the scroll controller and configure a unique component ID using .id.
61      List({
62        scroller: this.scroller
63      })// ...
64        .id(LIST_ID)
65          // 1.2 Obtain the scroll offset through a callback.
66        .onDidScroll(() => {
67          this.curYOffset = this.scroller.currentOffset().yOffset;
68        })
69        .onAreaChange((oldValue, newValue) => {
70          // 1.3 Obtain the width and height of the component.
71          this.listComponentWidth = newValue.width as number;
72          this.listComponentHeight = newValue.height as number;
73        })
74    }
75  }
76}
77```
78
79**Step 2: Implement recursive scrolling snapshot and caching.**
80
81Implement a recursive method to scroll and snapshot in a loop, combined with animation effects during scrolling.
82
83```ts
84  /**
85   * Recursively scroll, take snapshots, and merge all snapshots when scrolling ends.
86   */
87  async scrollSnapAndMerge() {
88    // Record the scroll offset.
89    this.scrollYOffsets.push(this.curYOffset - this.yOffsetBefore);
90    // Call the component snapshot API to obtain the List component's snapshot.
91    const pixelMap = await this.getUIContext().getComponentSnapshot().get(LIST_ID);
92    // Obtain the pixel bytes of the pixel map and save them in an array.
93    let area: image.PositionArea =
94      await this.getSnapshotArea(pixelMap, this.scrollYOffsets, this.listComponentWidth, this.listComponentHeight)
95    this.areaArray.push(area);
96
97    // Check whether scrolling has reached the end and if the user has forced a stop.
98    if (!this.scroller.isAtEnd() && !this.isClickStop) {
99      // If scrolling is not at the end or stopped, play a scroll animation, wait for a period, then continue recursive snapshot taking.
100      CommonUtils.scrollAnimation(this.scroller, 1000, this.scrollHeight);
101      await CommonUtils.sleep(1500);
102      await this.scrollSnapAndMerge();
103    } else {
104      // When scrolling ends, call mergeImage to stitch all saved pixel map data and return the long snapshot pixel map object.
105      this.mergedImage =
106        await this.mergeImage(this.areaArray, this.scrollYOffsets[this.scrollYOffsets.length - 1],
107          this.listComponentWidth, this.listComponentHeight);
108    }
109  }
110
111// src/main/ets/common/CommonUtils.ets
112static scrollAnimation(scroller: Scroller, duration: number, scrollHeight: number): void {
113  scroller.scrollTo({
114    xOffset: 0,
115    yOffset: (scroller.currentOffset().yOffset + scrollHeight),
116    animation: {
117      duration: duration,
118      curve: Curve.Smooth,
119      canOverScroll: false
120    }
121  });
122}
123```
124
125**Step 3: Stitch the long snapshot.**
126
127Call **image.createPixelMapSync()** to create a long snapshot **longPixelMap** and iterate through the previously saved image fragment data (**this.areaArray**). Construct an **image.PositionArea** object **area**, and call **longPixelMap.writePixelsSync(area)** to write these fragments into the correct positions one by one, thereby stitching them into a complete long snapshot.
128
129```ts
130async mergeImage(areaArray: image.PositionArea[], lastOffsetY: number, listWidth: number,
131  listHeight: number): Promise<PixelMap> {
132  // Create a long snapshot pixel map object.
133  let opts: image.InitializationOptions = {
134    editable: true,
135    pixelFormat: 4,
136    size: {
137      width: this.getUIContext().vp2px(listWidth),
138      height: this.getUIContext().vp2px(lastOffsetY + listHeight)
139    }
140  };
141  let longPixelMap = image.createPixelMapSync(opts);
142  let imgPosition: number = 0;
143
144
145  for (let i = 0; i < areaArray.length; i++) {
146    let readArea = areaArray[i];
147    let area: image.PositionArea = {
148      pixels: readArea.pixels,
149      offset: 0,
150      stride: readArea.stride,
151      region: {
152        size: {
153          width: readArea.region.size.width,
154          height: readArea.region.size.height
155        },
156        x: 0,
157        y: imgPosition
158      }
159    }
160    imgPosition += readArea.region.size.height;
161    longPixelMap.writePixelsSync(area);
162  }
163  return longPixelMap;
164}
165```
166
167**Step 4: Save the snapshot.**
168
169Use the **SaveButton** security component to save the snapshot.
170
171```ts
172// src/main/ets/view/SnapshotPreview.ets
173SaveButton({
174  icon: SaveIconStyle.FULL_FILLED,
175  text: SaveDescription.SAVE_IMAGE,
176  buttonType: ButtonType.Capsule
177})
178  .onClick((event, result) => {
179    this.saveSnapshot(result);
180  })
181
182async saveSnapshot(result: SaveButtonOnClickResult): Promise<void> {
183  if (result === SaveButtonOnClickResult.SUCCESS) {
184    const helper = photoAccessHelper.getPhotoAccessHelper(this.context);
185    const uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'png');
186    const file = await fileIo.open(uri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
187    const imagePackerApi: image.ImagePacker = image.createImagePacker();
188    const packOpts: image.PackingOption = {
189      format: 'image/png',
190      quality: 100,
191    };
192    imagePackerApi.packing(this.mergedImage, packOpts).then((data) => {
193      fileIo.writeSync(file.fd, data);
194      fileIo.closeSync(file.fd);
195      Logger.info(TAG, `Succeeded in packToFile`);
196      promptAction.showToast({
197        message: $r('app.string.save_album_success'),
198        duration: 1800
199      })
200    }).catch((error: BusinessError) => {
201      Logger.error(TAG, `Failed to packToFile. Error code is ${error.code}, message is ${error.message}`);
202    });
203  }
204  // ...
205}
206```
207
208**Step 5: Release the pixel map after saving.**
209
210When the **PixelMap** object is no longer in use, assign it to **undefined** in a timely manner, as in **this.mergedImage = undefined;**.
211
212```ts
213  closeSnapPopup(): void {
214    // Close the popup.
215    this.isShowPreview = false;
216    // Release the pixel map object.
217    this.mergedImage = undefined;
218    // Reset related parameters.
219    this.snapPopupWidth = 100;
220    this.snapPopupHeight = 200;
221    this.snapPopupPosition =
222      PopupUtils.calcPopupCenter(this.screenWidth, this.screenHeight, this.snapPopupWidth, this.snapPopupHeight);
223    this.isLargePreview = false;
224  }
225```
226
227### Encapsulating a Global Screenshot API
228As mentioned earlier, snapshot APIs must be used where the UI context is clear. However, applications sometimes need to encapsulate a unified global snapshot API for different modules. For example, in the following example, the component built by **awardBuilder** has a fixed structure. **GlobalStaticSnapshot** provides a global method **getAwardSnapshot** that meets the needs of different modules to take snapshots of components in the same fixed mode, achieving the encapsulation of a global snapshot API.
229
230```ts
231import { image } from '@kit.ImageKit';
232import { ComponentContent } from '@kit.ArkUI';
233
234export class Params {
235  text: string | undefined | null = "";
236
237  constructor(text: string | undefined | null) {
238    this.text = text;
239  }
240}
241
242@Builder
243function awardBuilder(params: Params) {
244  Column() {
245    Text(params.text)
246      .fontSize(90)
247      .fontWeight(FontWeight.Bold)
248      .margin({ bottom: 36 })
249      .width('100%')
250      .height('100%')
251  }.backgroundColor('#FFF0F0F0')
252}
253
254export class GlobalStaticSnapshot {
255  /**
256   * A static method to obtain a snapshot of a fixed object.
257   */
258  static getAwardSnapshot(uiContext: UIContext, textParam: Params): image.PixelMap | undefined {
259    let resultPixmap: image.PixelMap | undefined = undefined
260    let contentNode = new ComponentContent(uiContext, wrapBuilder(awardBuilder), textParam);
261    uiContext.getComponentSnapshot()
262      .createFromComponent(contentNode, 320, true, { scale: 1, waitUntilRenderFinished: true })
263      .then((pixmap: image.PixelMap) => {
264        resultPixmap = pixmap
265      })
266      .catch((err: Error) => {
267        console.error("error: " + err)
268      })
269    return resultPixmap;
270  }
271}
272```
273
274## Best Practices for Component Screenshot
275### Reasonably Controlling Snapshot Timing
276When implementing snapshot functionality, note that the component rendering process is not completed in one go. When building and displaying components, the system goes through complex steps, such as measurement, layout, and command submission, before finally presenting them on the screen during a hardware refresh. Therefore, in specific cases, calling the snapshot API immediately after component refresh may not obtain the expected content.
277
278To ensure accurate snapshot results, it is recommended that you execute the snapshot operation after the component is fully rendered.
279
280**Understanding the Component Drawing Status**
281
282To ensure the snapshot content meets expectations, you should understand when the code modifies the UI state and allow time for the system to process it, which can usually be achieved by adding a delay.
283
284Although you can use [ComponentObserver](../reference/apis-arkui/js-apis-arkui-inspector.md#componentobserver) in the Inspector to perceive component drawing (**draw**) and display notifications, note that the component drawing notification from **ComponentObserver** does not mean the system has actually executed the drawing commands, as this depends on the load of the graphics system service.
285
286**Waiting for Drawing to Complete**
287
288The main factor affecting the snapshot expectation is the time difference between the snapshot timing and the system service executing the drawing commands. When a snapshot call is initiated, all previously submitted drawing commands on the application side may not have been truly executed by the graphics service. To address this, you can specify **waitUntilRenderFinished** as **true** in the [SnapshotOptions](../reference/apis-arkui/js-apis-arkui-componentSnapshot.md#snapshotoptions12) parameter to ensure the system waits for all previous drawing commands to complete before executing the snapshot request, thereby capturing more complete content.
289
290> **NOTE**
291>
292> It is recommended that the **waitUntilRenderFinished** parameter be always set to **true**.
293
294**Understanding the Impact of Resource Loading on Snapshot**
295
296Another common reason for unexpected snapshots is image resource loading. Image components support both online resource links and local resources, and most image resources are in compressed formats such as PNG and JPEG. These resources need to be decoded by the system into a pixel map format that can be submitted for drawing, a process that occurs on an asynchronous I/O thread by default. This can lead to unexpected snapshot behavior due to the uncertainty of the process duration.
297
298The following optimization approaches can be taken:
2991. Pre-parse images into PixelMap format and configure the PixelMap for the image component. This approach is recommended for optimization.
3002. Set the **syncLoad** attribute of the **Image** component to **true** to force synchronous loading, ensuring resources can be directly submitted when the component is built.
3013. Specify the delay duration and set **checkImageStatus** to **true** to attempt to take a snapshot. If error 160001 is returned, retry with an increased delay.
302
303
304### Timely Saving and Releasing Pixel Map Objects
305To release resources promptly, assign the **PixelMap** object returned by the snapshot API to null when it is no longer in use.
306
307### Appropriately Controlling Sampling Precision
308Avoid capturing images that are excessively large, ideally not larger than the screen size. If the size of the image to capture exceeds device-specific underlying limits, the capture will fail. You can reduce sampling precision by controlling the **scale** parameter in **SnapshotOptions**, which significantly saves memory and improves snapshot efficiency.
309
310### Using Other Capabilities for Self-Rendering Scenarios
311Although snapshots can be taken by simply passing a component root node, this is not the recommended way when the child components include [Video](../reference/apis-arkui/arkui-ts/ts-media-components-video.md), [XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md), or [Web](../reference/apis-arkweb/arkts-basic-components-web.md) components. It is recommended that you use the [image.createPixelMapFromSurface](../reference/apis-image-kit/arkts-apis-image-f.md#imagecreatepixelmapfromsurface11) API.
312