螢幕擷取

Windows。Graphics.Capture命名空間提供 API,用於從顯示視窗或應用程式視窗擷取影格,建立影片串流或快照,以建立協作且互動的體驗。

透過螢幕擷取,開發者會啟動安全的系統介面,讓終端使用者選擇要擷取的顯示或應用程式視窗,系統會在被擷取項目周圍畫上黃色通知邊框。 若有多次同時捕獲,則會在每個被捕獲物品周圍畫出黃色邊框。

Note

螢幕擷取 API 僅支援 Windows 桌面裝置及 Windows Mixed Reality 沉浸式頭戴裝置。

本文說明如何擷取顯示視窗或應用程式視窗的單一影像。

檢查螢幕擷取支援

在嘗試擷取前,請確認目前裝置是否支援螢幕擷取。 在 GraphicsCaptureSession 使用 IsSupported 方法來判斷是否有螢幕擷取功能:

// Check if screen capture is supported
if (!GraphicsCaptureSession.IsSupported())
{
    // Hide capture UI
    CaptureControlsPanel.Visibility = Visibility.Collapsed;
    PreviewPlaceholderText.Text = "Screen capture isn't supported on this device.";
    UpdateStatus("Screen capture isn't supported on this device.");
    return;
}

螢幕擷取可能不被支援的原因有多種,包括裝置不符合硬體需求。

啟動系統介面開始螢幕擷取

使用 GraphicsCapturePicker 類別來呼叫系統選擇器的使用者介面。 最終使用者使用此介面選擇要擷取的顯示視窗或應用程式視窗。 選擇器回傳一個 GraphicsCaptureItem 用來建立擷取會話。

在 WinUI 3 應用程式中,您必須先用視窗句柄初始化選擇器,然後再呼叫 PickSingleItemAsync

// Launch picker and start capture
var picker = new GraphicsCapturePicker();
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd);
GraphicsCaptureItem item = await picker.PickSingleItemAsync();
if (item != null)
{
    StartCaptureInternal(item);
}

建立擷取幀池與擷取會話

使用 GraphicsCaptureItem 建立 一個 Direct3D11CaptureFramePool ,包含你的 D3D 裝置、支援的像素格式(DXGI_FORMAT_B8G8R8A8_UNORM)、所需影格數(可為任意整數)及影格大小。 GraphicsCaptureItem 類別的 Size 屬性可以作為你的幀大小:

// Create frame pool and capture session
_framePool = Direct3D11CaptureFramePool.Create(
    _canvasDevice,
    CaptureDirectXPixelFormat.B8G8R8A8UIntNormalized,
    BufferCount,
    item.Size);
_session = _framePool.CreateCaptureSession(item);

Note

在啟用 Windows HD 色彩的系統中,內容像素格式不一定是DXGI_FORMAT_B8G8R8A8_UNORM。 為避免像素過剪裁(即擷取內容看起來過淡),在擷取 HDR 內容時,建議對擷取管線中的每個元件使用 DXGI_FORMAT_R16G16B16A16_FLOAT ,包括 Direct3D11CaptureFramePool,目標目標如 CanvasBitmap。 視需求而定,可能需要額外處理,例如儲存為 HDR 內容格式或 HDR 轉 SDR 色調映射。 本文聚焦於SDR內容擷取。 欲了解更多資訊,請參閱 使用 DirectX 搭配高動態範圍顯示器及進階色彩

一旦使用者明確同意在系統介面中擷取應用程式視窗或顯示, GraphicsCaptureItem 即可與多個擷取會話關聯。 這樣你的應用程式就能選擇捕捉同一個項目,用於不同的體驗。

擷取擷取幀

當你的幀池和擷取會話建立好後,請在你的 GraphicsCaptureSession 實例上呼叫 StartCapture,通知系統開始將擷取幀傳送到你的應用程式。

要取得這些擷取框架,也就是 Direct3D11CaptureFrame 物件,請使用 Direct3D11CaptureFramePool.FrameArrived 事件:

// Handle frame arrival
_framePool.FrameArrived += OnFrameArrived;

建議避免在 FrameArrived 的 UI 執行緒上做大量工作,因為這個事件會在每次有新幀可用時觸發。 如果你選擇在 UI 執行緒上監聽 FrameArrived 事件,請留意每次事件觸發時所執行的工作量。

或者,你也可以用 Direct3D11CaptureFramePool.TryGetNextFrame 的方法手動拉取影格,直到你取得所有需要的影格。

Direct3D11CaptureFrame物件包含ContentSizeSurface 以及 SystemRelativeTimeSystemRelativeTime 是 QPC(QueryPerformanceCounter)時間,可用於同步其他媒體元素。

程序擷取幀

呼叫 TryGetNextFrame 時會檢查 Direct3D11CaptureFramePool 中的每一幀,並依 Direct3D11CaptureFrame 物件的壽命重新檢查。 對於受管理的應用程式,建議使用 Direct3D11CaptureFrame.Dispose 方法。 Direct3D11CaptureFrame 實作了 IDisposable 介面,因此處置該框架會將緩衝區歸還至集區。

應用程式不應儲存對 Direct3D11CaptureFrame 物件的參考,也不應在畫面重新簽入後儲存底層 Direct3D 表面的參考資料。

在這個範例中,每一幀都被轉換成 CanvasBitmap,這是 Win2D 函式庫的一部分:

// Convert frame to Win2D bitmap and display
CanvasBitmap canvasBitmap = CanvasBitmap.CreateFromDirect3D11Surface(
    _canvasDevice,
    frame.Surface);

底層 Direct3D 表面的大小一律為建立(或重新建立)Direct3D11CaptureFramePool時所指定的大小。 如果內容大於框,內容會被裁切至框的大小。 如果內容小於幀數,則幀的其餘部分包含未定義的資料。 建議應用程式用 ContentSize 屬性複製出該 Direct3D11CaptureFrame 的子矩形,以避免顯示未定義內容。

儲存截圖

有了 CanvasBitmap 後,你可以將其儲存為圖片檔案。 以下範例使用 FileSavePicker,將目前畫面儲存為 PNG 檔案。 在 WinUI 3 應用程式中,你必須用視窗 handle 初始化選擇器:

// Save screenshot
var savePicker = new FileSavePicker();
savePicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
savePicker.SuggestedFileName = "screen-capture";
savePicker.FileTypeChoices.Add("PNG image", new List<string> { ".png" });
WinRT.Interop.InitializeWithWindow.Initialize(savePicker, _hwnd);
StorageFile? file = await savePicker.PickSaveFileAsync();
if (file is not null)
{
    using var fileStream = await file.OpenAsync(FileAccessMode.ReadWrite);
    await frameToSave.SaveAsync(fileStream, CanvasBitmapFileFormat.Png, 1f);
}

因應擷取項目大小調整或裝置遺失

在擷取過程中,應用程式可能希望更改其 Direct3D11CaptureFramePool 的某些面向。 這包括提供新的 Direct3D 裝置、改變影格緩衝區大小,甚至改變池中緩衝區的數量。 在這些情境中,推薦的方法是對 Direct3D11CaptureFramePool 物件進行 Recreate 方法。

當呼叫 Recreate 時,所有現有的框架都會被丟棄。 這是為了避免將其底層 Direct3D 表面所屬的裝置交付給應用程式,而該應用程式可能已無法再存取這些裝置。 因此,在呼叫 Recreate 之前,先處理所有待處理的影格可能是明智的做法。

WinUI 3 注意事項

在將螢幕截圖程式碼從 UWP 遷移到 WinUI 3(Windows 應用程式 SDK)時,請注意以下差異:

  • 視窗控制代碼初始化 — 例如 GraphicsCapturePickerFileSavePicker 這類選擇器,必須使用 InitializeWithWindow 以視窗控制代碼進行初始化。 更多資訊請參見 「擷取視窗指柄(HWND)」。
  • UI 執行緒上的合成 API — 在 WinUI 3 中, CanvasComposition 表面操作(例如繪圖到 CompositionDrawingSurface)必須使用 DispatcherQueue.TryEnqueue 派遣到 UI 執行緒。 框架擷取與點陣圖建立可以在框架池的背景執行緒進行,但更新合成視覺化必須在 UI 執行緒上進行。
  • 命名空間變更 — 使用 Microsoft.UI.CompositionMicrosoft.UI.Xaml.HostingMicrosoft.UI.Dispatching,而非其對應的 Windows.UIWindows.Graphics.Capture命名空間保持不變。

參見