Skip to content
Apexify.jsDocs
Apexify.js version v5.4.5

Advanced / Scene · guide

Scene — overview

Current Apexify.js 6.0.0 documentation for Scene — overview.

apexify.jsRuntime: nodeCURRENTSince 6.0.0

Scene is Apexify.js's declarative composition graph: a positive width, positive height, optional background, and an ordered layers array. The same scene can render to PNG directly or feed the scene→GIF / scene→video helpers.

Types: SceneRenderInput, SceneLayer, SceneRenderOptions from apexify.js (or the type-only apexify.js/types subpath).

On ApexPainter: createScene, renderScene, renderSceneToGIF, renderSceneToVideoFrames, validateSceneRenderInput.

Higher-level templates, named assets, components, and plugins are documented in the Composition hub; they ultimately produce or transform normal scene data.


Validation is mandatory

Scene validation runs before rendering. There is no public validate: false bypass.

Validation covers more than root dimensions:

  • finite positive canvas/surface dimensions and canvas resource budgets,
  • aggregate scene pixel budget across the root and nested surfaces,
  • total scene layer count,
  • nested surface count/depth,
  • image count,
  • text-layer count and aggregate text content,
  • chart count,
  • remote asset count,
  • image/text domain validation,
  • finite numeric transform leaves and opacity ranges,
  • authoritative scene/surface dimensions (background configs may not redefine width/height).

SceneRenderOptions.maxSurfaceDepth may lower the configured maximum for one render, but cannot raise the global runtime limit.

Use validateSceneRenderInput(input, { maxSurfaceDepth? }) when you want to reject untrusted composition data before entering a larger workflow; the renderer will validate again at its own boundary.


SceneBuilder contract

createScene() returns a mutable builder with copy-on-ingress semantics. Caller-owned layer/background objects and Buffers are copied when added. A later mutation of the original input does not silently change the builder.

Stack methods include:

  • addLayer, addLayers
  • insertLayer, insertLayers
  • insertBefore, insertAfter
  • replaceLayer, replaceLayers
  • moveLayer
  • removeLayer, clearLayers
  • setBackground, clearBackground
  • layerCount

Invalid indices throw ApexifyInputError instead of being silently clamped.

toRenderInput() returns an isolated snapshot. Mutating the returned structure does not mutate the builder. render() also renders a snapshot, so later caller mutation cannot affect an already-started render.

SceneBuilder.render() leaves asset references unchanged by default. A builder created by ApexPainter.createScene() can opt in with { resolveAssetRefs: true }.


Paint order

Layer array order is stable bottom → top. Earlier entries paint first; later entries paint over them.

SOURCEtypescript
typescript
Studio
const png = await painter.renderScene({  width: 640,  height: 360,  background: { colorBg: "#0f172a" },  layers: [    // painted first    { type: "imageBuffer", buffer: backgroundTile, x: 0, y: 0, width: 640, height: 360 },    // painted on top    { type: "text", texts: { text: "Hello", x: 48, y: 80, fontSize: 36, color: "#fff" } },  ],});

Builder insert/move/replace operations change this array order directly; no hidden z-index is applied.


Nested surfaces

A surface is a child scene with explicit placement dimensions. Its background and child layers render into a child canvas, then that canvas is composited directly onto the parent with placement opacity/composite mode/rotation/scale.

Child surfaces remain canvases until parent compositing; Apexify.js does not PNG-encode then decode every nested surface. This avoids a major composition roundtrip while preserving clipping and transform isolation.

SOURCEtypescript
typescript
Studio
const scene = {  width: 800,  height: 450,  background: { colorBg: "#020617" },  layers: [    {      type: "surface",      placement: { x: 40, y: 40, width: 320, height: 180, opacity: 0.9 },      background: { colorBg: "#0f172a" },      layers: [        { type: "text", texts: { text: "Nested", x: 20, y: 40, fontSize: 24, color: "#f8fafc" } },      ],    },  ],};

The placement width/height are authoritative; a surface background must not define its own width/height.


Asset references

  • renderScene, renderSceneToGIF, renderSceneToVideoFrames: $refs resolve by default.
  • Pass resolveAssetRefs: false in the matching scene-render options when the scene is already resolved or when $ should remain literal data.
  • SceneBuilder.render: resolution is opt-in.

See Named assets for $$ escaping, dotted paths, Buffer refs, embedded scalar refs, and replacement semantics.


Outputs

OutputAPIBehavior
PNGrenderScene / SceneBuilder.render()Validate, compose ordered layers, encode final root PNG.
GIFrenderSceneToGIFValidate scene/GIF inputs, compose the scene frame, merge optional repeated tail frames, then encode GIF.
VideorenderSceneToVideoFramesValidate frame configuration before wasting scene raster work; compose/merge frames, then delegate to the video creator/FFmpeg.

Minimal builder example

SOURCEtypescript
typescript
Studio
import { ApexPainter } from "apexify.js";import fs from "node:fs"; const painter = new ApexPainter(); const png = await painter  .createScene(640, 360)  .setBackground({ colorBg: "#0f172a" })  .addLayers([    {      type: "text",      texts: { text: "Hello, Scene", x: 48, y: 160, fontSize: 36, color: "#e2e8f0" },    },  ])  .render(); fs.writeFileSync("./scene.png", png);

Chapter guide

#TopicLink
1Scene types & builderTypes & builder
2Image/text/path/imageBufferRaster & vector layers
3Chart layersChart layers
4Custom lines & surfacesConnectors & surfaces
5BackgroundsScene backgrounds
6Rendering & asset resolutionRender scene
7Scene → GIFScene → GIF
8Scene → videoScene → video
9Paint order & performancePaint order & performance

Next steps