Skip to content
Void Monster Docs

Type to search guides and API reference.

    libCamera/Developer docs

    Coordinates and framing

    Public 1.14.2API 1.14.2

    libCamera’s 2D API uses unscaled scene pixels. Screen pixels and grid tiles are different units: pass scene coordinates for positions and bounds, and use the framing options when you want a margin in tiles.

    CameraPosition describes the centre of the viewport. CameraBounds describes a rectangle by its top-left corner and size.

    Field Unit Meaning
    CameraBounds.x Scene pixels Left edge of the rectangle.
    CameraBounds.y Scene pixels Top edge of the rectangle.
    CameraBounds.width Scene pixels Horizontal size; must be greater than zero.
    CameraBounds.height Scene pixels Vertical size; must be greater than zero.
    CameraPosition.x, y Scene pixels Viewport centre.
    CameraPosition.scale Zoom factor Larger values zoom in; must be greater than zero.

    All values must be finite. A rectangle can have negative coordinates; its width and height must still be positive.

    // On a scene with a 100px grid, this rectangle is two tiles wide and three tall.
    const bounds = { x: 500, y: 300, width: 200, height: 300 };
    const view = camera.frame([bounds], { margin: 1 });
    if (view) {
    const result = await consumer.move({ ...view, duration: 500 });
    if (result.status !== 'completed') console.debug(result);
    }

    frame() only calculates a view. It does not move the camera or acquire ownership. For several rectangles, it fits the rectangle enclosing all of them.

    • margin adds space on each side, in grid tiles. It defaults to 0 and cannot be negative.
    • closest is the minimum number of horizontal tiles visible. It prevents zooming in too far.
    • widest is the maximum number of horizontal tiles visible. It prevents zooming out too far, so it can crop the requested region.

    Both zoom limits must be finite and positive. If widest is smaller than closest, closest takes precedence.

    const view = camera.frame([bounds], { margin: 1, closest: 8, widest: 30 });

    frame() returns undefined for an empty list, invalid bounds or options, or an unavailable viewport. Check the result before moving.

    camera.frameTokens(tokenIds, options) accepts token IDs on the current scene. These are token IDs, not actor IDs or document UUIDs. Every requested token must exist and must not be explicitly hidden from this client. Framing reads document geometry during redraws and floor changes, even when a token has no rendered placeable. An empty list or more than 1000 IDs produces no view.

    const ids = canvas.tokens.controlled.map(token => token.id);
    const view = camera.frameTokens(ids, { margin: 1, closest: 8 });
    if (view) await consumer.move({ ...view, duration: 500 });

    Use camera.tokenPosition(tokenId) for a token’s centre, or camera.gridPosition(row, column) for a grid cell’s centre. Grid indices must be safe integers. Both helpers return undefined when the needed canvas or object is unavailable.

    bounds: true keeps the viewport inside the scene rectangle. Pass a CameraBounds object instead to constrain it to a smaller region.

    await consumer.move({ x: 600, y: 450, duration: 500, bounds: true });

    The constraint can increase zoom to fit the viewport inside the rectangle. This differs from frame(), which calculates a view showing the requested region.

    The released signatures are CameraBounds, CameraPosition, and FrameOptions.

    camera.fitScene(consumer.id, options) acquires control, fits the current scene to this client’s screen, and releases its claim when the movement finishes.

    const result = await camera.fitScene(consumer.id, { duration: 500 });

    For a sequence that already holds a claim, use claim.fitScene(options, signal) instead. It replaces the claim’s current movement and leaves ownership with that claim. Release it when your sequence ends. Both operations support duration, easing, level, bounds and lockInput. An unavailable scene returns a rejected movement result.