Offscreen Capture¶
This example renders a point scene offscreen and writes one PNG.
Preview¶

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 |