HarmonyOS多窗口截图合并方案
==================

#### 问题现象

当前页面由两个窗口组成，截图时仅能获取其中一个窗口的内容，无法实现与用户手动截图一致的效果。

#### 效果预览

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/2b/v3/lJgNXgTlTDa6BnCJzGGEuQ/zh-cn_image_0000002628604264.png?HW-CC-KV=V1&HW-CC-Date=20260709T134005Z&HW-CC-Expire=86400&HW-CC-Sign=E7D099FE27800D342E38C57053E85E22D2A86F404E4B35C914133C0382584A9A "点击放大")

#### 背景知识

*   [Window](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-window)：窗口提供管理窗口的一些基础能力，包括对当前窗口的创建、销毁、各属性设置，以及对各窗口间的管理调度。用于生成子窗口。
*   [snapshot](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-window-window#snapshot9)：获取窗口级别截图。
*   [Canvas](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-components-canvas-canvas)：提供画布组件，用于自定义绘制图形。将图片在画布组件上进行合并实现与手动截图相同的效果。

#### 解决方案

使用snapshot分别获取每个窗口的截图，再根据窗口的叠加顺序，将截图按照顺序使用drawImage绘制在Canvas上的同一位置，然后获取截图。

*   主要页面（截图以及绘制合并主要逻辑）。
    
    import { BusinessError } from '@kit.BasicServicesKit';
    import { window } from '@kit.ArkUI';
    import { image } from '@kit.ImageKit';
    import { common } from '@kit.AbilityKit';
    
    @Entry
    @Component
    struct Index {
      private context \= this.getUIContext().getHostContext(); _//_ _获取Context_
      mainWindowClass: window.Window | undefined \= undefined;
      private settings: RenderingContextSettings \= new RenderingContextSettings(true);
      private canvasContext: CanvasRenderingContext2D \= new CanvasRenderingContext2D(this.settings);
    
      aboutToAppear(): void {
      _  // 获取主窗口_
        window.getLastWindow(this.context).then((value) \=\> {
          this.mainWindowClass \= value;
        });
      }
    
      build() {
        Row() {
          Column() {
            TextInput({ placeholder: '输入文本后可复制，也可以粘贴到此处' })
              .width('90%')
              .margin(5);
            Button('弹出水印子窗口')
              .width('90%')
              .margin(20)
              .onClick(() \=\> {
                try {
                  let context \= this.getUIContext().getHostContext() as common.UIAbilityContext;
                  context.windowStage.createSubWindow('subWindowTest')
                    .then((win: window.Window) \=\> {
                      win.setUIContent('pages/Watermark');
                      win.setWindowFocusable(false);
                      win.setWindowTouchable(false);
                      win.showWindow().then(() \=\> {
                        win.setWindowBackgroundColor('#00000000');
                      });
                    })
                    .catch((err: BusinessError) \=\> {
                      console.info(\`Failed to create the subwindow. Cause code: ${err.code}, message: ${err.message}\`);
                    });
                } catch (exception) {
                  console.info(\`Failed to create the subwindow. Cause code: ${exception.code}, message: ${exception.message}\`);
                }
              });
            Button("截图")
              .width('90%')
              .margin({ bottom: 20 })
              .onClick(async () \=\> {
           _     // 清除画布内容_
                this.canvasContext.clearRect(0, 0, 1000, 1000);
               _ // 主窗口截图_
                this.mainWindowClass?.snapshot((err: BusinessError, pixelMap: image.PixelMap) \=\> {
                  const errCode: number \= err.code;
                  if (errCode) {
                    console.error(\`Failed to snapshot window. Cause code: ${err.code}, message: ${err.message}\`);
                    return;
                  }
                 _ // 绘制主窗口到画布_
                  this.canvasContext.drawImage(pixelMap, 90, 50, 200, 400);
                });
                window.getLastWindow(this.context, (err: BusinessError, topWindow) \=\> {
                  const errCode: number \= err.code;
                  if (errCode) {
                    return;
                  }
                  topWindow.snapshot((err: BusinessError, pixelMap: image.PixelMap) \=\> {
                    const errCode: number \= err.code;
                    if (errCode) {
                      console.error(\`Failed to snapshot window. Cause code: ${err.code}, message: ${err.message}\`);
                      return;
                    }
                   _ // 绘制子窗口到画布_
                    this.canvasContext.drawImage(pixelMap, 90, 50, 200, 400);
                  });
                });
              });
            Canvas(this.canvasContext)
              .width('100%')
              .height('100%')
              .backgroundColor('#ffdedbdb');
          }
          .width('100%');
        }
        .height('100%');
      }
    }
    

*   子窗口（示例窗口，无关键逻辑）。
    
    @Entry
    @Component
    struct Watermark {
      canvas: CanvasRenderingContext2D \= new CanvasRenderingContext2D(new RenderingContextSettings(true));
    
      build() {
        Column() {
          Column() {
            Canvas(this.canvas)
              .width('100%')
              .height('100%')
              .hitTestBehavior(HitTestMode.Transparent)
              .onReady(() \=\> {
                this.canvas.fillStyle \= '#ff000000';
                this.canvas.font \= '16vp';
                this.canvas.textAlign \= 'center';
                this.canvas.textBaseline \= 'middle';
              _  // 在这里绘制文字水印，也可以是图片水印_
                for (let i \= 0; i < this.canvas.width / 120; i++) {
                  this.canvas.translate(120, 0);
                  let j \= 0;
                  for (; j < this.canvas.height / 120; j++) {
                    this.canvas.rotate(\-Math.PI / 180 \* 30);
                   _ // 此处水印数据是写死的，具体请替换为自己的水印_
                    this.canvas.fillText('test', -60, -60);
                    this.canvas.rotate(Math.PI / 180 \* 30);
                    this.canvas.translate(0, 120);
                  }
                  this.canvas.translate(0, -120 \* j);
                }
              })
          }
          .backgroundColor(Color.White)
          .borderRadius(20)
          .width('100%')
          .height('100%')
          .justifyContent(FlexAlign.Center)
        }
        .opacity(0.1)
        .justifyContent(FlexAlign.Center)
        .height('100%')
        .width('100%')
        .backgroundColor('#60000000')
        .transition(TransitionEffect.OPACITY.animation({ duration: 300 }))
      }
    }