目录

How do I check the specific limitation when ComponentSnapshot returns error code 100001, indicating that the screenshot size is too large and exceeds the hardware limitation?

The hardware limitations vary depending on the platform. You can run the following command to view the limitations:

hdc shell hidumper -s 10 -a 'vktextureLimit'

The common value is width: 8192 height: 8192, indicating that the length and width of the maximum drawing texture size must be within 8192 pixels. Compare the size of the component to be captured to determine whether the screenshot failure is caused by this reason. If yes, adjust the size of the component to be captured or combine the screenshots after scrolling. For details, please refer to Capturing Long Content (Scrolling Snapshot) and Long Screenshot.

To capture a long screenshot of an off-screen component, perform the following steps:

// src/main/ets/utils/Utils.ets
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';

export class Utils {
  static sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  // Calculate the valid screenshot area
  static async getSnapshotArea(context: UIContext, pixelMap: PixelMap, scrollYOffsets: number[], listWidth: number,
    listHeight: number): Promise<image.PositionArea> {
    let stride = pixelMap.getBytesNumberPerRow();
    let bytesNumber = pixelMap.getPixelBytesNumber();
    let buffer: ArrayBuffer = new ArrayBuffer(bytesNumber);
    let len = scrollYOffsets.length;

    if (scrollYOffsets.length >= 2) {
      let realScrollHeight = scrollYOffsets[len-1] - scrollYOffsets[len-2];
      if (listHeight - realScrollHeight > 0) {
        let cropRegion: image.Region = {
          x: 0,
          y: context.vp2px(listHeight - realScrollHeight) || 0,
          size: {
            height: context.vp2px(realScrollHeight) || 0,
            width: context.vp2px(listWidth) || 0
          }
        };
        await pixelMap.crop(cropRegion);
      }
    }

    let area: image.PositionArea = {
      pixels: buffer,
      offset: 0,
      stride: stride,
      region: {
        size: {
          width: 0,
          height: 0
        },
        x: 0,
        y: 0
      }
    }

    try {
      let imgInfo = pixelMap.getImageInfoSync();
      area.region.size.width = imgInfo.size.width;
      area.region.size.height = imgInfo.size.height;
      pixelMap.readPixelsSync(area);
    } catch (err) {
      let error = err as BusinessError;
      console.error(`getSnapshotArea err, code:${error.code}, message: ${error.message}`);
    }
    return area;
  }

  // Graphic splicing
  static async mergeImage(context: UIContext, areaArray: image.PositionArea[], lastOffsetY: number, listWidth: number,
    listHeight: number): Promise<PixelMap> {
    let opts: image.InitializationOptions = {
      editable: true,
      pixelFormat: 4,
      size: {
        width: context.vp2px(listWidth) || 0,
        height: context.vp2px(lastOffsetY + listHeight) || 0
      }
    };
    let longPixelMap = image.createPixelMapSync(opts);
    let imgPosition: number = 0;

    for (let i = 0; i < areaArray.length; i++) {
      let readArea = areaArray[i];
      let area: image.PositionArea = {
        pixels: readArea.pixels,
        offset: 0,
        stride: readArea.stride,
        region: {
          size: {
            width: readArea.region.size.width,
            height: readArea.region.size.height
          },
          x: 0,
          y: imgPosition
        }
      }
      imgPosition += readArea.region.size.height;
      try {
        longPixelMap.writePixelsSync(area);
      } catch (err) {
        let error = err as BusinessError;
        console.error(`writePixelsSync err, code:${error.code}, message: ${error.message}`);
      }
    }
    return longPixelMap;
  }
}
// src/main/ets/pages/Index.ets
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { Utils } from '../utils/Utils';

export class MyDataSource {
  private _data: string[] = [];
  private _listeners: DataChangeListener[] = [];

  pushData(data: string): void {
    this._data.push(data);
    this._listeners.forEach(listener => {
      listener.onDataAdd(this._data.length - 1);
    })
  }

  getAllData(): string[] {
    return this._data;
  }

  totalCount(): number {
    return this._data.length;
  }

  getData(index: number): string {
    return this._data[index];
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    this._listeners.push(listener);
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    const index = this._listeners.indexOf(listener);
    if (index != -1) {
      this._listeners.splice(index, 1);
    }
  }
}

@Entry
@Component
struct SnapshotExample {
  private scroller: Scroller = new Scroller();
  private listComponentWidth: number = 0;
  private listComponentHeight: number = 0;
  @State mergedImage: PixelMap | undefined = undefined;
  private areaArray: image.PositionArea[] = [];
  private scrollYOffsets: number[] = [];
  private data: MyDataSource = new MyDataSource();
  private listId: string = 'LIST_ID';

  aboutToAppear(): void {
    for (let i = 0; i < 50; i++) {
      this.data.pushData(`Hello ${i}`);
    }
  }

  async onceSnapshot() {
    await this.beforeSnapshot();
    await this.snapAndMerge();
    this.afterGeneratorImage();
  }

  async snapAndMerge() {
    try {
      // Record the current scrolling position
      this.scrollYOffsets.push(this.scroller.currentOffset().yOffset);
      // Take a screenshot of the current display part of the component
      const pixelMap = await this.getUIContext().getComponentSnapshot().get(this.listId);
      // Calculate the valid screenshot area
      let area: image.PositionArea =
        await Utils.getSnapshotArea(this.getUIContext(), pixelMap, this.scrollYOffsets, this.listComponentWidth,
          this.listComponentHeight);
      this.areaArray.push(area);
      // Determine whether to scroll to the bottom
      if (!this.scroller.isAtEnd()) {
        // Not to the bottom: Scroll down by one screen height
        this.scroller.scrollTo({
          xOffset: 0,
          yOffset: (this.scroller.currentOffset().yOffset + this.listComponentHeight),
          animation: {
            duration: 200
          }
        });
        await Utils.sleep(200);
        await this.snapAndMerge();
      } else {
        this.mergedImage =
          await Utils.mergeImage(this.getUIContext(), this.areaArray, this.scrollYOffsets[this.scrollYOffsets.length-1],
            this.listComponentWidth, this.listComponentHeight);
      }
    } catch (err) {
      let error = err as BusinessError;
      console.error(`snapAndMerge err, code:${error.code}, message: ${error.message}`);
    }
  }

  async beforeSnapshot() {
    try {
      this.scroller.scrollTo({
        xOffset: 0,
        yOffset: 0,
        animation: {
          duration: 200
        }
      });
      await Utils.sleep(200);
    } catch (err) {
      let error = err as BusinessError;
      console.error(`beforeSnapshot err, code:${error.code}, message: ${error.message}`);
    }
  }

  afterGeneratorImage() {
    this.scrollYOffsets.length = 0;
    this.areaArray.length = 0;
  }

  build() {
    Column({ space: 12 }) {
      Button('Click to get the snapshot')
        .onClick(() => {
          this.onceSnapshot();
        })
      Stack() {
        // Screenshot component
        List({ space: 12, scroller: this.scroller }) {
          LazyForEach(this.data, (item: string) => {
            ListItem() {
              Row() {
                Text(item)
                  .fontSize(50)
                  .height(50)
              }
            }
          }, (item: number) => item.toString())
        }
        .scrollBar(BarState.Off)
        .cachedCount(3)
        .width('100%')
        .height('100%')
        .backgroundColor($r('sys.color.background_secondary'))
        .id(this.listId)
        .onAreaChange((oldValue, newValue) => {
          this.listComponentWidth = newValue.width as number;
          this.listComponentHeight = newValue.height as number;
        })
        // Set the Z-sequence to -1 to ensure that this component is invisible
        .zIndex(-1)

        // Use a mask to cover the screenshot area
        Column()
          .width('100%').height('100%').backgroundColor(Color.White)
        // Long screenshot
        Scroll() {
          Image(this.mergedImage)
        }
      }
      .width('100%')
      .layoutWeight(1)
    }
  }
}