Skip to content

Offscreen Capture

This example renders a point scene offscreen and writes one PNG.

Preview

Offscreen Capture

Run And Adapt

Commands below assume a Datoviz source checkout and start at the repository root. Use your configured build environment; Python routes additionally require local bindings.

Route Availability Command or action
C Canonical native source just example-c runtime/offscreen_capture (build and run), or rerun ./build/examples/c/runtime/offscreen_capture
Python Available; direct-engine adaptation python3 -m examples.python.gallery.runtime.offscreen_capture
Browser Native only writes a native offscreen PNG through the app capture path

This example is approved as a starting point for user code and coding agents. Keep the object lifetimes and data shapes intact while adapting the data and styling.

What To Look For

Four point positions, colors, and pixel diameters are uploaded once, then an offscreen view is created at the requested 1920x1080 output size. The code checks the framebuffer dimensions before calling render-once and PNG capture, so the saved image should match the requested pixel size exactly.

This is the runtime path to copy for reproducible batch figures, tests, and documentation captures that should not depend on an onscreen window.

Source

#!/usr/bin/env python3
"""Render a retained point scene offscreen and optionally write a PNG."""

from __future__ import annotations

import argparse
import ctypes
import os
from pathlib import Path

import numpy as np

import datoviz as dvz

from examples.python.gallery import common as ex


WIDTH = 1920
HEIGHT = 1080
POINT_COUNT = 4


def _add_points(scene, panel) -> None:
    positions = np.array(
        [
            [-0.55, -0.35, 0.0],
            [-0.18, +0.35, 0.0],
            [+0.18, -0.20, 0.0],
            [+0.55, +0.25, 0.0],
        ],
        dtype=np.float32,
    )
    colors = ex.color_array(ex.CYAN, ex.BLUE, ex.YELLOW, ex.TEXT)
    diameters = np.array([38.0, 54.0, 44.0, 62.0], dtype=np.float32)

    point = dvz.dvz_point(scene, 0)
    if not point:
        raise RuntimeError("dvz_point() failed")
    if dvz.dvz_visual_set_data_many(
        point,
        {
            "position": positions,
            "color": colors,
            "diameter_px": diameters,
        },
    ) != 0:
        raise RuntimeError("dvz_visual_set_data_many(point) failed")
    ex.add_visual(panel, point)


def _build_scene():
    scene = dvz.dvz_scene()
    if not scene:
        raise RuntimeError("dvz_scene() failed")
    figure = dvz.dvz_figure(scene, WIDTH, HEIGHT, 0)
    if not figure:
        raise RuntimeError("dvz_figure() failed")
    panel = dvz.dvz_panel_full(figure)
    if not panel:
        raise RuntimeError("dvz_panel_full() failed")
    dvz.dvz_panel_set_background_color(panel, ex.BG)
    _add_points(scene, panel)
    return scene, figure


def _framebuffer_size(view) -> tuple[int, int]:
    width = ctypes.c_uint32()
    height = ctypes.c_uint32()
    dvz.dvz_view_framebuffer_size(view, ctypes.byref(width), ctypes.byref(height))
    return int(width.value), int(height.value)


def _render_capture(output: Path | None = None):
    scene, figure = _build_scene()
    app = None
    try:
        app = dvz.dvz_app(scene)
        if not app:
            raise RuntimeError("dvz_app() failed")
        view = dvz.dvz_view_offscreen(app, figure, WIDTH, HEIGHT)
        if not view:
            raise RuntimeError("dvz_view_offscreen() failed")

        framebuffer_width, framebuffer_height = _framebuffer_size(view)
        if (framebuffer_width, framebuffer_height) != (WIDTH, HEIGHT):
            raise RuntimeError(
                f"unexpected framebuffer size: {framebuffer_width}x{framebuffer_height}"
            )

        if dvz.dvz_view_render_once(view) != dvz.DVZ_CANVAS_FRAME_READY:
            raise RuntimeError("dvz_view_render_once() failed")
        rgba = dvz.dvz_view_capture_rgba(view)
        if ex.SMOKE_MODE and not np.any(rgba[..., :3] != rgba[0, 0, :3]):
            raise RuntimeError("offscreen capture smoke is blank")

        if output is not None:
            output.parent.mkdir(parents=True, exist_ok=True)
            if dvz.dvz_view_capture_png(view, str(output).encode()) != 0:
                raise RuntimeError("dvz_view_capture_png() failed")
        return rgba
    finally:
        if app:
            dvz.dvz_app_destroy(app)
        dvz.dvz_scene_destroy(scene)


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    smoke_capture = os.environ.get("DVZ_PYTHON_GALLERY_CAPTURE", "") if ex.SMOKE_MODE else ""
    default_output = Path(smoke_capture) if smoke_capture else None
    parser.add_argument("--output", type=Path, default=default_output)
    args = parser.parse_args(argv)

    rgba = _render_capture(args.output)
    action = f"wrote {args.output}" if args.output is not None else "rendered without writing"
    print(f"offscreen_capture: {action} ({rgba.shape[1]}x{rgba.shape[0]} exact pixels)")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
/*
 * Copyright (c) 2021 Cyrille Rossant and contributors. All rights reserved.
 * Licensed under the MIT license. See LICENSE file in the project root for details.
 * SPDX-License-Identifier: MIT
 */

/* offscreen_capture - This example renders a retained point scene offscreen and writes one PNG.
 *
 * What to look for: four point positions, colors, and pixel diameters are uploaded once, then an
 * offscreen view is created at the requested 1920x1080 output size. The code checks the framebuffer
 * dimensions before calling render-once and PNG capture, so the saved image should match the
 * requested pixel size exactly.
 *
 * This is the runtime path to copy for reproducible batch figures, tests, and documentation
 * captures that should not depend on an onscreen window.
 *
 * Scenario: runtime_offscreen_capture
 * Style: runtime, graphite_cyan, 1920x1080 output target
 *
 * Build:  just example-c runtime/offscreen_capture
 * Run:    ./build/examples/c/runtime/offscreen_capture
 */



/*************************************************************************************************/
/*  Includes                                                                                     */
/*************************************************************************************************/

#include <stdint.h>

#include "datoviz/app.h"
#include "datoviz/canvas/enums.h"
#include "datoviz/scene.h"
#include "example_common.h"
#include "example_style.h"



/*************************************************************************************************/
/*  Constants                                                                                    */
/*************************************************************************************************/

#define WIDTH  EXAMPLE_OUTPUT_WIDTH
#define HEIGHT EXAMPLE_OUTPUT_HEIGHT
#define POINT_COUNT 4u



/*************************************************************************************************/
/*  Main                                                                                         */
/*************************************************************************************************/

int main(int argc, char** argv)
{
    (void)argc;

    int ret = 1;
    DvzScene* scene = NULL;
    DvzApp* app = NULL;

    char png_path[512] = {0};
    example_outpath(argv[0], "offscreen_capture.png", png_path, sizeof(png_path));

    scene = dvz_scene();
    EXAMPLE_CHECK(scene != NULL, "dvz_scene() failed");

    DvzFigure* figure = dvz_figure(scene, WIDTH, HEIGHT, 0);
    DvzPanel* panel = figure != NULL ? dvz_panel_full(figure) : NULL;
    DvzVisual* point = dvz_point(scene, 0);
    EXAMPLE_CHECK(
        figure != NULL && panel != NULL && point != NULL, "failed to create scene objects");
    example_graphite_cyan_set_panel_background(panel);

    vec3 positions[POINT_COUNT] = {
        {-0.55f, -0.35f, 0.0f},
        {-0.18f, +0.35f, 0.0f},
        {+0.18f, -0.20f, 0.0f},
        {+0.55f, +0.25f, 0.0f},
    };
    DvzColor colors[POINT_COUNT] = {
        example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_ACCENT_PRIMARY),
        example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_ACCENT_SECONDARY),
        example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_WARNING),
        example_graphite_cyan_color(EXAMPLE_STYLE_COLOR_TEXT),
    };
    float diameters[POINT_COUNT] = {38.0f, 54.0f, 44.0f, 62.0f};
    DvzVisualDataUpdate updates[] = {
        {.attr_name = "position", .data = positions, .item_count = POINT_COUNT},
        {.attr_name = "color", .data = colors, .item_count = POINT_COUNT},
        {.attr_name = "diameter_px", .data = diameters, .item_count = POINT_COUNT},
    };
    EXAMPLE_CHECK(dvz_visual_set_data_many(point, updates, 3) == 0, "failed to upload point data");
    EXAMPLE_CHECK(dvz_panel_add_visual(panel, point, NULL) == 0, "dvz_panel_add_visual() failed");

    app = dvz_app(scene);
    EXAMPLE_CHECK(app != NULL, "dvz_app() failed (no GPU?)");

    DvzView* view = dvz_view_offscreen(app, figure, WIDTH, HEIGHT);
    EXAMPLE_CHECK(view != NULL, "dvz_view_offscreen() failed");
    uint32_t framebuffer_width = 0;
    uint32_t framebuffer_height = 0;
    dvz_view_framebuffer_size(view, &framebuffer_width, &framebuffer_height);
    EXAMPLE_CHECK(
        framebuffer_width == WIDTH && framebuffer_height == HEIGHT,
        "offscreen framebuffer size must match requested PNG pixels");
    EXAMPLE_CHECK(
        dvz_view_render_once(view) == DVZ_CANVAS_FRAME_READY, "dvz_view_render_once() failed");
    EXAMPLE_CHECK(dvz_view_capture_png(view, png_path) == 0, "dvz_view_capture_png() failed");

    dvz_fprintf(stdout, "offscreen_capture: wrote %s (%ux%u exact pixels)\n", png_path, WIDTH, HEIGHT);
    ret = 0;

cleanup:
    if (app != NULL)
        dvz_app_destroy(app);
    if (scene != NULL)
        dvz_scene_destroy(scene);
    return ret;
}
Example details
  • ID: runtime_offscreen_capture
  • Category: runtime
  • Lane: runtime
  • Status: supported
  • Source: examples/c/runtime/offscreen_capture.c
  • Approved adaptation starter: yes
  • Python source: examples/python/gallery/runtime/offscreen_capture.py
  • Python adaptation: Available; direct-engine adaptation
  • Browser support: Native only
  • Browser note: writes a native offscreen PNG through the app capture path
  • Browser capability tags: native-capture
  • Validation: smoke+screenshot

Data

Field Value
kind synthetic