Rendering Offscreen in Processing with PGraphics: Buffers, Compositing, and File Export
Use Processing's PGraphics class to draw offscreen: create the buffer once, wrap drawing in beginDraw/endDraw, composite with image(), and export with pg.save().
04 Sept 2026, 00:59 UTC

If you need to draw something in Processing without showing it immediately — a trail layer, a print-resolution export, a pre-rendered texture — the visible canvas is the wrong tool. PGraphics gives you a second, offscreen drawing surface with the full Processing API. You draw into it, composite it onto the main canvas with image(), or save it straight to disk. This guide covers the create-draw-composite-save workflow in Java mode, including the state and threading rules that trip people up.
What a PGraphics buffer actually is
A PGraphics object is an independent sketch surface living in memory. It has its own width, height, renderer, and — critically — its own drawing state. Fill color, stroke weight, transformations, and text settings on the main canvas do not carry over, and vice versa. Depending on the renderer, the buffer is either a CPU-backed image (JAVA2D) or an OpenGL texture (P2D/P3D).
Prerequisites
- Processing 3.x or 4.x in Java mode. Details of
smooth(),pixelDensity(), andhint()differ between major versions, so verify on your installed release. - A sketch whose renderer you know. Match the buffer's renderer to the main sketch's. Mixing (for example a
P3Dbuffer in aJAVA2Dsketch) can silently fall back or produce blank output. - Enough memory for the buffer. Very large buffers can exceed GPU texture limits or heap size; scale up incrementally rather than jumping straight to print resolution.
Step 1: Create the buffer once in setup()
Call createGraphics() and keep the reference in a field:
PGraphics pg;
void setup() {
size(800, 600, P2D);
pg = createGraphics(400, 400, P2D); // match the sketch renderer
}
The two-argument form createGraphics(w, h) uses the default renderer; the three-argument form lets you specify JAVA2D, P2D, or P3D. Create the buffer once and reuse it. Calling createGraphics() every frame allocates a new image or texture each time, which causes garbage-collection pressure and visible frame drops — you can confirm this by printing frameRate with both approaches.
Step 2: Draw between beginDraw() and endDraw()
Every drawing call targeting the buffer must sit inside a beginDraw()/endDraw() pair:
void draw() {
pg.beginDraw();
pg.clear(); // reset the buffer; omit for trail effects
pg.fill(255, 0, 0);
pg.noStroke();
pg.ellipse(mouseX % 400, mouseY % 400, 60, 60);
pg.endDraw();
background(30);
image(pg, 200, 100); // composite onto the main canvas
}
Three rules matter here:
- Never skip
endDraw(). Without it the buffer is left in an undefined state and may not display or save correctly. - Set all state inside the block.
fill(),stroke(),translate(), and friends apply per-buffer, so configure them afterbeginDraw(). - Clear deliberately.
pg.clear()(orpg.background()with an alpha value) resets the buffer. Skipping it makes content accumulate across frames — a bug if unwanted, a cheap trail effect if intentional.
Step 3: Save the buffer independently of the screen
pg.save("output.png") writes only the offscreen content, not the main canvas. Because the buffer can be larger than the display, this is the standard way to produce high-resolution output:
void keyPressed() {
if (key == 's') {
pg.save("export-" + frameCount + ".png");
}
}
Relative paths resolve against the sketch folder or data path depending on the call, which is a common source of "where did my file go" confusion. Use an absolute path when the destination matters.
Threading constraint for P2D and P3D
OpenGL-backed buffers are textures bound to the sketch's animation thread. Create and draw them only from setup(), draw(), and event handlers on that thread. Touching a P2D/P3D buffer from a worker thread causes flicker or crashes. If you need background processing, export pixel data and hand that to the other thread instead of the buffer itself.
Checking the result
- Run the minimal sketch above and confirm a red ellipse appears on the main canvas — that verifies creation, the draw block, and compositing.
- Press
sand confirm the PNG exists in the sketch folder and shows only the ellipse on a transparent background, not the dark main-canvas background. - Watch the console for renderer or OpenGL warnings, especially on first run or after switching Processing versions.
Recovery options
Because a PGraphics buffer is just an object, recovery is simple: if output looks wrong, blank it with pg.beginDraw(); pg.clear(); pg.endDraw(); or recreate it with a fresh createGraphics() call (e.g., after a size change). No sketch state outside the buffer is affected. If a large buffer fails to allocate, reduce dimensions or switch from P2D to JAVA2D to move the memory cost from GPU to heap.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.