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