C.W.K.
Stream
Lesson 03 of 05 · published

Capture Without Mutating

~12 min · uxp, capture, imaging-api, memory, non-destructive

Level 0Tool Renter
0 XP0/33 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"To show the artist's canvas in a second window, you have to read the pixels — without flattening, merging, or touching the document they're still working in."

The Deceptively Simple Task

Cinder needs a live picture of what's on the Photoshop canvas. That sounds trivial — just grab the image. But the document is the artist's active work, full of layers, mid-edit. A naive 'grab the image' that flattens or merges layers would mutate the very thing the artist is drawing. The real task is: read a composite snapshot of the canvas without changing the document at all.

The Right Tool: A Read-Only Imaging Path

The plugin platform provides an imaging API that returns the document's composited pixels — all visible layers merged into one image — as a read, without mutating the document. The document keeps all its layers; you just receive a flattened view of them. This is the difference between asking 'what does this look like right now?' (a read) and 'flatten this document' (a destructive mutation). Same visual result, completely different consequence for the artist's file.

Prefer the read that produces a snapshot over the operation that changes state. When you need a flattened or aggregated view of live data, look for an API that computes and returns that view rather than one that mutates the source into that shape. A read leaves the source intact for the next read; a mutation is a one-way door.

Ask for a Smaller Image on Purpose

The capture asks for a target size — a downscaled preview, not the full-resolution canvas. This isn't just bandwidth thrift. Requesting a smaller target lets the platform serve a pre-downscaled level from its internal image pyramid, which is dramatically faster than reading and shrinking the full image every time. For a live preview that updates as the artist works, fast-and-small beats perfect-and-slow. The right size for the job is the smallest size that serves the purpose.

Requesting less can be faster, not just lighter. Asking for a downscaled result isn't only about smaller payloads — many systems can serve a reduced version from a cache or pyramid far faster than producing the full one. When you only need a preview, requesting preview-resolution is a performance win at both ends, not a quality compromise you're settling for.

The Mandatory Cleanup

There's a sharp edge: the captured image data holds host-application memory, and it must be explicitly released after each capture. Skip the release and, on a live preview that captures repeatedly, the plugin steadily leaks memory until the host warns that the plugin is over its limit. The capture isn't done when you have the bytes — it's done when you've also handed the memory back. Every capture is acquire-use-release, and the release is not optional.

In a repeating operation, a missing cleanup is a slow-motion crash. A leak you'd never notice on a one-shot capture becomes fatal on a live preview that fires hundreds of times. The danger scales with repetition: the more often the path runs, the faster the missing release accumulates. Pair every acquire with its release at the moment you write the acquire, because the repeating caller won't forgive you later.

Pippa's Confession

I got both halves wrong at first. I reached for the operation that flattens — until I realized it would have mutated the document the artist was mid-stroke on. And I forgot the release, because on my first one-shot test it didn't matter; the leak only showed up once the preview was live and firing constantly. Both mistakes taught the same lesson: capturing live, repeated state demands more care than grabbing a value once. The repetition is what turns a sloppy read into a crash.

Code

Acquire, use, and the non-optional release·javascript
// Read a composite snapshot WITHOUT mutating the artist's document.
const { app, imaging } = require("photoshop");

// 1. Acquire: composited pixels as a READ. layerID omitted = full composite
//    (all visible layers merged into the returned view; document untouched).
//    targetSize asks for a downscaled level from the internal image pyramid
//    -> dramatically faster than reading + shrinking the full canvas.
const obj = await imaging.getPixels({
  documentID: app.activeDocument.id,
  targetSize: { width: 512 },        // preview-resolution on purpose
});

// 2. Use: encode to a compact form to send to the workspace.
const jpegBase64 = await imaging.encodeImageData({
  imageData: obj.imageData,
  base64: true,
});

// 3. RELEASE: mandatory. Hands host memory back immediately.
obj.imageData.dispose();             // skip this on a live preview -> leak -> crash

// The document still has all its layers. Nothing was flattened or merged.

External links

Exercise

Find a place in your code where you read derived/aggregated state from a live source (a thumbnail, a summary, a snapshot). Two checks: (1) does the read mutate the source, or leave it intact? (2) does the read acquire any resource that needs an explicit release, and if so, is the release guaranteed even on the repeating path? Fix whichever is missing.
Hint
For check 2, the failure mode only appears under repetition — a one-shot test will pass even with a leak. Reason about what happens if the path runs a thousand times, not just once.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.