scenic-draft-playwright
v0.15.0
Published
Render a scenic-draft scene to an image file from Node, by path-tracing it in a headless browser driven by Playwright.
Maintainers
Readme
scenic-draft-playwright
scenic-draft scenes, path-traced
to image files from Node.
import { backgrounds, materials, plane, sphere } from 'scenic-draft-fluent';
import { renderToFile } from 'scenic-draft-playwright';
const SCENE = {
scene: sphere(1).paint(materials.chrome).union(plane([0, 1, 0], -1)),
background: backgrounds.dusk,
};
await renderToFile(SCENE, 'renders/chrome.png', { size: 1200, maxFrames: 900, seed: 7 });One call opens a headless Chromium, traces the scene until the accumulation reaches its sample budget, writes the file and shuts the browser down again — from a script, in CI, on a machine with no screen.
There is no second renderer here. The copy of scenic-draft already in your
project is served to the page and its own render() does the tracing, so a
saved image and the same scene on a web page come out of the same code.
Install
npm install scenic-draft-playwright scenic-draft-fluent playwright
# or: pnpm add scenic-draft-playwright scenic-draft-fluent playwright
npx playwright install chromiumBoth dependencies are peers, and both are bounded from below only:
| Peer | Range | Why |
| --- | --- | --- |
| scenic-draft-fluent | >= this package's version | It carries scenic-draft, and the copy it resolves is the one that gets served to the page. |
| playwright | >=1.40 | Its browsers are your project's to install and version. |
Run the script with anything that runs TypeScript — tsx render.ts, node
--experimental-strip-types render.ts — or write it in JavaScript and run it
with nothing at all. The package ships ESM with TypeScript declarations.
renderToFile(spec, output, options?)
Traces one scene and writes it. The encoding comes from the file's extension —
.png, .jpg, .jpeg or .webp — unless format says otherwise, and any
directories in the path are created. Returns { path, format, width, height,
frames, bytes }.
renderToBuffer(spec, options?) is the same render handed back as
{ data: Buffer, … }, for something that is going to upload it rather than
keep it. format defaults to png there, there being no filename to read.
| Option | | |
| --- | --- | --- |
| size | number | Square backing resolution in pixels (default 1024). |
| width height | number | The same, when the image is not square. |
| maxFrames | number | Samples per pixel to accumulate (default 1200). |
| bounces | number | Light-bounce budget (default 6); glass and metal reward 8–12. |
| seed | number | Fix the sample sequence. Without one, two runs differ in their noise. |
| format | 'png' \| 'jpeg' \| 'webp' | Overrides the extension. |
| quality | number | Encoder quality in (0, 1], for jpeg and webp. |
| timeout | number | Milliseconds before the render is abandoned (default 300000). |
| onProgress | (frames, total) => void | After every accumulated frame, in your process. |
| launch | LaunchOptions | Passed to chromium.launch(), on top of this package's own arguments. |
| browser | Browser | A Playwright browser to draw in, instead of launching one. |
| libraryRoot | string | The scenic-draft package root to serve. Resolved for you by default. |
Everything the scene itself can do is unchanged — this package adds no
vocabulary and re-exports none. Build scenes with scenic-draft-fluent (or with
scenic-draft directly) and hand them over; either dialect is accepted, and a
fluent camera(...) is normalised on the way in.
openStudio(options?)
Launching the browser is most of the cost of the first image and all of the cost of the rest, so a script writing more than one should open a studio and keep it:
import { openStudio } from 'scenic-draft-playwright';
const studio = await openStudio();
try {
for (const [index, scene] of SCENES.entries()) {
await studio.renderToFile(scene, `renders/plate-${index}.png`, { size: 900, seed: 3 });
}
} finally {
await studio.close();
}The studio takes the browser options (launch, browser, libraryRoot,
timeout) and exposes the same renderToFile / renderToBuffer, each on a page
of its own — so each render gets its own WebGL2 context and gives it back. Given
an existing browser, the studio never launches or closes anything, which is
what makes it usable from inside a Playwright test.
What it actually does
It launches Chromium, serves a page holding one canvas, and hands that page your
copy of scenic-draft as ES modules, resolved off disk from the fluent package
you installed. The page imports it and calls render(); when the accumulation
finishes the canvas is read back and the bytes are written. There is no HTTP
server and no temporary directory — the requests are intercepted by Playwright
and answered from memory.
The browser is asked for software rasterisation (--use-angle=swiftshader)
rather than whatever GPU happens to be there: a build machine usually has none,
and one that does would otherwise produce a subtly different image from every
machine that does not. Between that and seed, the same script gives the same
file everywhere — which is what makes a rendered image something a repository
can hold.
The cost is speed. Software path tracing is minutes, not seconds, for a large
image at a high sample count, so raise timeout and report progress:
await renderToFile(SCENE, 'renders/poster.png', {
size: 1600,
maxFrames: 2000,
timeout: 30 * 60 * 1000,
onProgress: (frames, total) => process.stdout.write(`\r${frames}/${total}`),
});If a machine really does have a GPU worth using, launch goes straight to
Playwright and the last occurrence of a Chromium switch wins, so the defaults can
be overridden one at a time:
await renderToFile(SCENE, 'renders/plate.png', { launch: { args: ['--use-gl=desktop'] } });DEFAULT_BROWSER_ARGS and DEFAULT_TIMEOUT are exported, for a script that
wants to say what it is changing.
Documentation
/headless— this package, with four worked examples- Guide and API reference — the scene vocabulary
scenic-draft-fluent— the chainable API these examples are written inscenic-draft-react— the same renderer, on a canvas in a component
Licence
MIT
