Lesson 14 — Special Modules: Pixel Access, Brushes & More
This lesson surveys the SDK modules that extend the core API with powerful but less commonly discovered capabilities: direct pixel access, brush inspection, binary buffers, and document metadata. Each module is introduced with its key types, access patterns, and a practical example.
These modules unlock whole categories of scripts the core API alone can't support — pixel-level image processing, binary data handling, and document metadata inspection.
Each module is self-contained and solves a different problem, so this lesson works as a survey rather than a single narrative. Each section covers what the module contains, what problem it solves, the correct access pattern, and a minimal example. /pixelaccessor gets the full treatment in Lesson 14; font enumeration is covered in full in Lesson 15.
One module deserves special attention: /pixelaccessor. Its access pattern isn't obvious from the name — you don't instantiate a reader directly, you obtain one with the free functions createPixelReader(bitmap) / createPixelReaderWriter(bitmap), or with the typed PixelReaderWriterRGBA8.create(bitmap). Knowing that up front avoids the natural but wrong assumption that you're looking for a class constructor.
Use this lesson as a map. When a future script needs something you're not sure the SDK provides, this is the first place to look.
What this lesson covers
This lesson documents the SDK's supporting modules that don't fit in the earlier lessons — direct pixel read/write, per-node metadata, document/drawing-scale properties, brushes, image resources, picture frames, timers, unit conversion, hatch patterns, system and environment modules, and the additional node types. Font enumeration is covered in Lesson 15.
/pixelaccessor— read and write individual pixels/taginterface— per-node custom metadata/documentproperties,/drawingscale,/marginsinterface— document properties/rasterbrush,/pathbrush— brush property access/imageresourceinterface— placed-image file metadata/pictureframeinterface— picture-frame state/rasterselection— pixel-selection state/timers—Timerobjects/units—UnitValueConverterDPI conversions/hatch— building hatch patterns/os,/environment,/logging,/configuration,/inspect— system modulesDocumentCommandmethod reference,AddChildNodesCommandBuilder- Additional node types —
TableTextNode,PatternRasterNode,EnclosureRasterNode,MeasurementNode,DevelopNode
Modules at a glance
| Module | What it adds |
|---|---|
/pixelaccessor | Read and write individual pixels in any colour format |
/fonts | Font enumeration — covered in full in Lesson 15 |
/rasterbrush | Inspect and modify raster brush properties |
/buffer | Binary buffer creation and manipulation |
/rasterobject | Raster object wrapper (used by brushes) |
/documentproperties | Document metadata — DPI, size, colour space |
/drawingscale | Drawing scale (map scale) access |
/taginterface | Per-node custom key-value tag storage |
/marginsinterface | Margin information on spread nodes |
/pageboxinterface | Page box (bleed, trim, slug) access |
/artboardproperties | Artboard metadata |
/imageresourceinterface | Placed-image source file metadata |
/pictureframeinterface | Picture-frame state on vector nodes |
/rasterselection | Read-only pixel-selection state |
/timers | Timer objects behind setTimeout/setInterval |
/units | UnitValueConverter DPI-based unit conversion |
/hatch | HatchPattern / HatchLine construction |
/os | Host OS info — platform, arch, version, line endings |
/environment | Runtime permissions, filesystem roots, SDK version |
/logging | File-based logging via LogFile |
/configuration | Parse JSON config into a typed tree |
/inspect | Node.js-style inspect for deep object introspection |
/pathbrush | Vector path brush property access |
/pixelaccessor — Direct pixel read/write
Read and write individual pixels across all of Affinity's colour formats. The accessor class must match the bitmap's format exactly — mismatches throw INVALID_ARGS.
'use strict';
const { Document } = require('/document.js');
const { DocumentCommand } = require('/commands.js');
const { PixelReaderWriterRGBA8 } = require('/pixelaccessor.js');
const doc = Document.current;
const node = doc?.selection.nodes.filter(n => n.isRasterNode || n.isImageNode).first;
if (!node) { console.log('Select a raster or image node.'); }
else {
const ri = node.rasterInterface;
// Always check format before creating an accessor — mismatch throws INVALID_ARGS
if (String(ri.format) !== 'RGBA8') {
console.log('Format is', ri.format, '— this example requires RGBA8.');
console.log('Open an RGBA8 image (File > New or a standard JPEG/PNG) and run again.');
} else {
const bitmap = ri.createCompatibleBitmap(true); // true = copy contents
const rw = PixelReaderWriterRGBA8.create(bitmap);
// Read a pixel — returns { r, g, b, alpha } all 0–255
// Note: the alpha key is 'alpha', not 'a'
const px = rw.readPixel(10, 10);
console.log('Pixel at (10,10):', px.r, px.g, px.b, px.alpha);
// Write a pixel
rw.writePixel(10, 10, { r: 255, g: 0, b: 0, alpha: 255 });
// Push changes back — see 14a-pixel-inverter.js for a full loop example
doc.executeCommand(DocumentCommand.createReplaceBitmap(doc.selection, bitmap));
console.log('Pixel written.');
}
}
All supported pixel formats
| Class | Format | Bits/channel | Notes |
|---|---|---|---|
PixelReaderRGBA8 / PixelReaderWriterRGBA8 | RGBA | 8-bit | Standard web/screen |
PixelReaderRGBA16 / PixelReaderWriterRGBA16 | RGBA | 16-bit | High precision |
PixelReaderIA8 / PixelReaderWriterIA8 | Intensity+Alpha | 8-bit | Greyscale |
PixelReaderIA16 / PixelReaderWriterIA16 | Intensity+Alpha | 16-bit | Greyscale HD |
PixelReaderCMYKA8 / PixelReaderWriterCMYKA8 | CMYK+Alpha | 8-bit | |
PixelReaderLABA16 / PixelReaderWriterLABA16 | Lab*+Alpha | 16-bit | Perceptual |
PixelReaderM8 / PixelReaderWriterM8 | Mask | 8-bit | Single channel |
PixelReaderM16 / PixelReaderWriterM16 | Mask | 16-bit | Single channel HD |
PixelReaderRGBAuf / PixelReaderWriterRGBAuf | RGBA | Float | HDR |
PixelReaderMf / PixelReaderWriterMf | Mask | Float | HDR single channel |
PixelReaderWriterRGBA8.create(bitmap)throwsINVALID_ARGSif the format isn'tRGBA8. Always checknode.rasterInterface.formatbefore creating an accessor. UseString(ri.format)to compare — the format value is an enum object, not a plain string.
Open the image as a document (File > Open) rather than placing it on a page. A placed image has isImageNode: true and isRasterNode: undefined — createReplaceBitmap silently does nothing on it. If you need to process a placed image in-place, rasterise it first via DocumentCommand.createRasteriseObjects(doc.selection, false, false).
/taginterface — Per-node custom metadata
TagInterface lets you write arbitrary string key-value pairs to any node via DocumentCommand.
These persist inside the .af file. You can also read them back at runtime through node.tagInterface
(hasKey, getValueForKey, hasPredefinedKey, getValueForPredefinedKey). See
Lesson 03 — Per-node tag metadata for the full read/write pattern.
'use strict';
const { PredefinedTagKey } = require('/taginterface.js');
const { DocumentCommand } = require('/commands.js');
const { Document } = require('/document.js');
const doc = Document.current;
if (!doc) { console.log('No document open.'); }
else if (doc.selection.nodes.isEmpty) { console.log('Select a node first.'); }
else {
const sel = doc.selection;
const node = sel.nodes.first;
// Write a custom tag (persists in the file)
doc.executeCommand(
DocumentCommand.createSetTagValueForKey(sel, 'my-script-data', 'hello world')
);
// Write a predefined tag
doc.executeCommand(
DocumentCommand.createSetTagValueForPredefinedKey(
sel, PredefinedTagKey.ScriptLabel, 'my-label')
);
// Read a tag back
const tagIface = node.tagInterface;
console.log('Has my-script-data:', tagIface.hasKey('my-script-data'));
console.log('Value:', tagIface.getValueForKey('my-script-data'));
}
/documentproperties — Document metadata
'use strict';
const { Document } = require('/document.js');
// Document properties are accessed directly on the doc object
const doc = Document.current;
if (!doc) { console.log('No document open.'); }
else {
console.log('DPI: ', doc.dpi);
console.log('Width (px): ', doc.widthPixels);
console.log('Height (px): ', doc.heightPixels);
console.log('Format: ', doc.format);
console.log('Colour profile:', doc.colourProfile);
}
doc.documentPropertiesdoes not exist as a property. Document metadata is read directly fromdoc.dpi,doc.widthPixels,doc.format, etc. TheDocumentPropertiesexport from/commandsis an internal type used bycreateSetDocumentProperties— it is not user-constructable.
/drawingscale — Drawing/map scale
For technical drawing and mapping workflows:
'use strict';
const { DrawingScale } = require('/drawingscale.js');
// Create a 1:100 scale
const scale = DrawingScale.createFromIntegers(1, 100, true);
console.log(scale.toString(false, true, false)); // "1 : 100"
console.log('Left:', scale.leftValue);
console.log('Right:', scale.rightValue);
// Get default scales for a unit
// IMPORTANT: pass a raw integer — NOT UnitType.* (those return undefined and cause INVALID_ARGS)
// 8 = Millimetres, 9 = Centimetres, 15 = Metres, 11 = Inches, 5 = Pixels
const defaults = DrawingScale.getDefaults(8); // 8 = Millimetres
for (const s of defaults) {
console.log(s.toString(true, true, false));
}
// Identity scale (1:1)
const identity = DrawingScale.identity;
DrawingScale.createFromString('1:50', …) parses a ratio string, and DrawingScale.enumerateDefaults(units, callback) enumerates the defaults from getDefaults via callback instead of returning an array.
/marginsinterface — Spread margins
Current limitation:
spread.marginsInterfacereturnsundefined— theMarginsInterfaceclass is defined withhasMarginsanduseMarginsproperties, butMarginsInterfaceApihas no exported methods, making it inaccessible at runtime.
Page geometry can be accessed via spread.physicalRootPropertiesInterface:
'use strict';
const { Document } = require('/document.js');
const doc = Document.current;
if (!doc) { console.log('No document open.'); }
else {
const spread = doc.currentSpread;
// Available page geometry
const prpi = spread.physicalRootPropertiesInterface;
const props = spread.physicalRootInterface?.physicalRootProperties;
console.log('Page count:', prpi?.pageCount);
// Page bounding box (in document pixels)
const pbi = prpi?.pageBoxInterface;
if (pbi) {
const box = pbi.getPageBoundingBox(0); // index 0 = first page
console.log('Page box:', box);
}
}
/rasterbrush — Brush property access
RasterBrush objects are returned from other API calls (for example, from a node's brush state). The class is fully functional once you have a handle:
'use strict';
const { RasterBrush, BrushDynamic, BrushDynamicControllerType, RasterBrushTextureMode } = require('/rasterbrush.js');
// Once you have a brush handle, inspect its properties:
// console.log('Size value:', brush.size.value);
// console.log('Opacity:', brush.opacity);
// console.log('Spacing:', brush.spacing);
// console.log('Blend mode:', brush.blendMode);
/os — Host operating system info
Read the host platform, architecture, OS version, and line-ending convention. Static properties only — no instance needed.
'use strict';
const { OS } = require('/os.js');
console.log('Platform:', OS.platform); // "Darwin", "Windows", "Linux"
console.log('Arch: ', OS.arch); // "arm64", "x64"
console.log('Machine: ', OS.machine);
console.log('Release: ', OS.release);
console.log('OS name: ', OS.osName, OS.osVersion);
console.log('EOL: ', JSON.stringify(OS.Eol)); // "\n" or "\r\n"
/environment — Runtime permissions & config
Environment reports what the script is allowed to do (filesystem, network, AI) and which folders it can touch, plus the internal SDK version.
'use strict';
const { Environment, EnvironmentPermission } = require('/environment.js');
console.log('Permissions:', Environment.permissions); // { fileSystem, network, genAI }
console.log('Can use network:', Environment.hasPermission(EnvironmentPermission.Network));
console.log('FS roots:', Environment.fileSystemRoots); // ["/Users/you/Desktop"]
console.log('SDK ABI:', Environment.sdkVersionStr); // internal SDK ABI string, not the app version
console.log('V8 version:', Environment.v8VersionStr);
console.log('Heap:', Environment.getHeapStatistics());
// Run a task after the current synchronous turn
Environment.postTask(() => console.log('queued'));
// Environment.quit(); // exit the app
Environment.enumerateFileSystemRoots(callback) enumerates the same roots via callback instead of returning the array.
Environment.sdkVersionStris the internal SDK ABI version, not the app version. Useapp.shortVersionfrom/applicationfor the Affinity build number.
/logging — File-based logging
LogFile mirrors the console output to a file at a given verbosity level. Create it, start(), write logs via the normal console.* methods, then flush() and stop().
'use strict';
const { LogFile, LogLevel } = require('/logging.js');
const { app } = require('/application.js');
const log = LogFile.create(app.userDesktopPath + '/script.log', LogLevel.Info, true);
log.start();
console.info('This is captured');
console.debug('This is excluded (below Info level)');
log.flush();
log.logLevel = LogLevel.Error; // raise the threshold mid-run
log.stop();
LogLevel values: All, Trace, Debug, Info, Warn, Error, None.
/configuration — Typed JSON config
ConfigurationItem.createFromJsonString(json, allowComments) parses JSON into a typed tree you can read with getProperty, getArrayItem, and typed accessors.
'use strict';
const { ConfigurationItem } = require('/configuration.js');
const cfg = ConfigurationItem.createFromJsonString(
'{ "width": 1200, "title": "Poster", "tags": ["a", "b"] }'
);
console.log(cfg.value); // { width: 1200, title: "Poster", ... }
console.log(cfg.getPropertyInt('width')); // 1200
console.log(cfg.getPropertyString('title')); // "Poster"
console.log(cfg.getArrayItem('tags')?.value); // ["a", "b"]
console.log(cfg.isObject, cfg.isArray);
/inspect — Node.js-style object inspector
/inspect provides a Node.js-compatible util.inspect for the Affinity runtime. It produces readable string representations of any value — including HandleObject instances whose internal state lives behind prototype getters. Getters that throw render as <error: ...> rather than failing the whole inspect.
'use strict';
const { inspect } = require('/inspect.js');
// Basic usage
console.log(inspect(doc));
console.log(inspect(doc.selection.nodes.first, { depth: 3 }));
// Format string (printf-style: %s %d %i %f %j %o %O)
const { format } = require('/inspect.js');
console.log(format('Node: %s, Type: %s', name, type));
// Automatically format all console output
const { patchConsole } = require('/inspect.js');
patchConsole({ depth: 4, getters: true });
console.log(doc); // now formatted by inspect
// ... later, when done:
const { unpatchConsole } = require('/inspect.js');
unpatchConsole();
Key options: depth (default 2, null = unlimited), showHidden (non-enumerable), getters (true/'get'/'set'), sorted (lexicographic sort), enums ('brief' shows RGBA8, 'full' shows RasterFormat.RGBA8, 'raw' shows the enum object).
/pathbrush — Vector path brush access
PathBrush exposes a vector brush's width, size/opacity variance, and corner behaviour. Like RasterBrush, there is no createDefault() — obtain a brush from an existing node's brush state.
'use strict';
const { PathBrush, CornerStrategy, PathBrushDynamicControllerType } = require('/pathbrush.js');
// Obtain a PathBrush from a node's brush state:
// const brush = node.pathBrushInterface?.pathBrush;
// console.log(brush.brushWidth, brush.sizeVariance, brush.opacityVariance);
// console.log(brush.cornerStrategy); // CornerStrategy.Pull/Fold/Overlap
PathBrush.createDefault()throws — same asRasterBrush. Brushes are read from node state, not constructed from scratch.CornerStrategy= Pull/Fold/Overlap;PathBrushDynamicControllerType= None/Pressure/Velocity/VelocityInverse.
/imageresourceinterface — Placed-image metadata
node.imageResourceInterface exposes everything about the source file behind an image node: its path, byte size, file type, original DPI, ICC profile, placement mode, and thumbnails. Available on ImageNode and EmbeddedDocumentNode.
'use strict';
const { Document } = require('/document.js');
const doc = Document.current;
const node = doc?.selection.nodes.first;
if (!node) { console.log('Select a node.'); }
else {
const iri = node.imageResourceInterface;
if (!iri) { console.log('Not a placed image.'); }
else {
console.log('Path: ', iri.imageFilePath ?? '(embedded)');
console.log('Bytes: ', iri.imageFileSize);
console.log('Type: ', iri.fileTypeName);
console.log('Original DPI:', iri.originalDPI);
console.log('Placement: ', iri.imagePlacement);
console.log('Modified: ', iri.modifiedTime);
}
}
saveOriginalFile(filename) writes the original source file back out to Desktop, and getSmallThumbnail() / getLargeThumbnail() return preview bitmaps — handy for building asset-catalogue scripts.
/pictureframeinterface — Picture-frame state
Vector nodes can act as picture frames — rectangles that clip content placed inside them. node.pictureFrameInterface reads that state; DocumentCommand.createPopulatePictureFrame(contentNode, selection) places a node into the frame programmatically.
// Snippet — assumes node is a vector node
const pfi = node.pictureFrameInterface;
if (pfi) {
console.log('Frame enabled: ', pfi.enabled);
console.log('Has contents: ', pfi.hasFrameContents);
if (pfi.hasFrameContents) {
console.log('Contents: ', pfi.frameContents?.descriptionInterface?.description);
console.log('Anchor: ', pfi.anchor);
}
}
calculateAnchor(node, hint) and calculateConstraints(node, hint) reproduce the fitting logic the UI uses when content is placed, letting you predict how an image will sit inside the frame before committing.
/rasterselection — Pixel-selection state
doc.rasterSelection returns a read-only snapshot of the current pixel-selection state. Use it to avoid running selection-mutating commands pointlessly — for example, skipping rasterInvertSelection when nothing is selected.
// Snippet — assumes doc is defined
const rs = doc.rasterSelection;
console.log('Has pixel selection:', rs.isCurrentPixelSelection);
console.log('Is select-all/none: ', rs.isSelectAllOrNone);
console.log('Is select-none: ', rs.isSelectNone);
The state-changing commands themselves (createRasterSelectAll, createSetRasterSelectionFromPolygon, …) are covered in Lesson 14.
/timers — Timer objects
Beyond the setTimeout/setInterval shims (covered in Lesson 12), the /timers module exposes the underlying Timer class for high-resolution scheduling.
'use strict';
const { Timer } = require('/timers.js');
const t = Timer.create();
t.expiryFromNow = 2.5; // seconds from now
t.waitAsync((err) => {
if (!err) console.log('2.5s elapsed:', Timer.now);
});
Timer.now / Timer.nowBigInt read the monotonic clock, Timer.cancelAll() clears every pending timer, and setTimeout(delay, callback) returns a Timer you can cancel() later. Remember the shim's argument order — delay first, callback second.
/units — UnitValue and conversion factors
doc.unitValueConverter converts between unit types using the document DPI — essential whenever the UI shows points but the SDK works in document pixels.
'use strict';
const { Document } = require('/document.js');
const { UnitType } = require('/units.js');
const doc = Document.current;
if (doc) {
const converter = doc.unitValueConverter;
console.log('DPI:', converter.dpi, '| view DPI:', converter.viewDpi);
const pxPerPt = converter.getConversionFactor(UnitType.Point, UnitType.Pixel);
console.log('1 pt =', pxPerPt, 'px');
const mmPerInch = converter.getConversionFactor(UnitType.Inch, UnitType.Millimetre);
console.log('1 in =', mmPerInch, 'mm');
}
UnitValue itself (re-exported from /units) carries prototype helpers: getValueAsPixels(converter), getValueAsUnitType(converter, unitType), getValueAsDegrees(), getValueAsRadians(), and isFinite.
/hatch — Building hatch patterns
Lesson 05 showed how to apply a hatch fill; the /hatch module is where the patterns themselves come from. A HatchPattern is a list of HatchLines, each with an origin, rotation, step distance, and optional dash pattern — and patterns are fully constructible, unlike brushes.
'use strict';
const { HatchPattern, HatchLine } = require('/hatch.js');
const pattern = HatchPattern.createEmpty();
const line = HatchLine.create(
{ x: 0, y: 0 }, // origin
Math.PI / 4, // rotation (radians)
8, // step between lines
[4, 2] // dash pattern (4 on, 2 off) — omit for solid
);
pattern.appendHatchLine(line);
console.log('Lines in pattern:', pattern.hatchLineCount);
Pass the finished pattern to HatchFill.create(pattern, units, penColour, brushColour, lineWeight) from Lesson 05. HatchPattern.createDefault() and .all (enumerating the built-in library patterns) are also available.
DocumentCommand — full method reference
commands.js exposes 43 static factory methods across the following groups:
Macro recording/playback
// Snippet
DocumentCommand.createStartRecordingMacro()
DocumentCommand.createStopRecordingMacro()
DocumentCommand.createReplayMacro()
DocumentCommand.createClearMacro()
DocumentCommand.createImportMacro(path)
DocumentCommand.createExportMacro(path)
Raster selection commands
// Snippet
DocumentCommand.createRasterSelectAll()
DocumentCommand.createRasterDeselect()
DocumentCommand.createRasterInvertSelection()
DocumentCommand.createRasterReselect()
DocumentCommand.createSetRasterSelectionFromPolygon(polygon, operation, antialias, feather) // replaces createRasterSelectPolygon
Opacity control (separate from visibility)
// Snippet
DocumentCommand.createSetOpacity(selection, opacity) // layer opacity
DocumentCommand.createSetBrushFillOpacity(selection, opacity, options) // fill opacity
DocumentCommand.createSetLineFillOpacity(selection, opacity, options) // stroke opacity
Fill anchor control
// Snippet
DocumentCommand.createSetBrushFillIsAnchoredToSpread(selection, bool, options)
DocumentCommand.createSetPenFillIsAnchoredToSpread(selection, bool, options)
DocumentCommand.createSetTransparencyFillIsAnchoredToSpread(selection, bool, options)
Tag metadata
// Snippet
DocumentCommand.createSetTagValueForKey(selection, key, value)
DocumentCommand.createSetTagValueForPredefinedKey(selection, predefinedKey, value)
Blend options
// Snippet
DocumentCommand.createSetBlendGamma(selection, gamma)
DocumentCommand.createSetBlendRanges(selection, blendOptions)
DocumentCommand.createSetAntialiasingMode(selection, antialiasingMode)
// Read the current values via node.blendModeInterface.blendOptions:
// .gamma, and spline-controlled channel ranges
// (getChannelSourceLayerRanges / getChannelUnderlyingCompositionRanges)
// Build range splines with Spline.createFromProfile(profile) /
// Spline.createFromPoints(points), then insertPoint / replaceOrInsertPoint.
Document-level
// Snippet
DocumentCommand.createSetDocumentProperties(documentProperties)
DocumentCommand.createConvertDocumentFormat(format)
DocumentCommand.createGroupTransform(selection, xDataOrNull, yDataOrNull)
DocumentCommand.createSetWindingMode(selection, windingOrder)
DocumentCommand.createSetExportConfig(selection, exportConfig)
DocumentCommand.createMoveNodes(selection, target, moveType, childListType)
DocumentCommand.createHideSelection(selection)
DocumentCommand.createFlatten()
DocumentCommand.createMergeVisible()
DocumentCommand.createSplitCurve(selection, parametricDistance)
DocumentCommand.createInsertGlyph(selection, glyph)
Snapshots
// Snippet
DocumentCommand.createAddDocumentSnapshot(description)
DocumentCommand.createDeleteDocumentSnapshot(snapshot)
DocumentCommand.createRestoreDocumentSnapshot(snapshot)
DocumentCommand.createSetCurrentSnapshot(snapshot)
DocumentCommand.createSetCurrentSnapshotFromHistoryItem(historyItem)
// Open a snapshot as a new document: snapshot.createDocument(),
// or async via snapshot.createDocumentAsync(callback).
History navigation
// Snippet
DocumentCommand.createSetHistoryIndex(index)
DocumentCommand.createCycleAlternateFutures(index)
createSetEditable — argument order
Selection is the first argument:
// Snippet
// CORRECT: selection first, editable flag second
DocumentCommand.createSetEditable(selection, true) // unlock
DocumentCommand.createSetEditable(selection, false) // lock
AddChildNodesCommandBuilder — insertion options
Setters for controlling insertion behaviour:
'use strict';
const { AddChildNodesCommandBuilder, InsertionMode } = require('/commands.js');
const builder = AddChildNodesCommandBuilder.create();
builder.autoName = true; // auto-assign layer name
builder.forcePixelAlignment = true; // snap to pixel grid
builder.clearCurrentSelection = false; // don't deselect before adding
builder.appendCurrentSelection = true; // add to existing selection
builder.setInsertionTarget(targetNode); // insert relative to a node
builder.setInsertionTargetSelection(targetSel); // insert relative to a selection
builder.setInsertionMode(InsertionMode.After); // Before / After / Into
addContainerNode adds a container node, and createCommandAndReset allows reusing the builder.
SDK native binding modules
Two native C++ binding modules underpin the pixel and brush APIs:
| Module | Contains |
|---|---|
affinity:raster | All PixelReader*Api and PixelReaderWriter*Api classes |
affinity:brushes | RasterBrushApi, BrushDynamicApi, brush enums |
New node types
Three new concrete node classes are available:
TableTextNode
A grid-based text node with cells separated by HardBreakType.TableCell glyphs. See Lesson 07 for the full pattern.
// Snippet — assumes doc is defined
const { TableTextNodeDefinition } = require('/nodes.js');
const def = TableTextNodeDefinition.create({ x: 0, y: 0, width: 300, height: 200 }, { width: 3, height: 3 });
doc.addNode(def);
PatternRasterNode
A raster node that tiles a repeating pattern. Useful for procedural fill effects.
// Snippet — assumes doc is defined
const { PatternRasterNodeDefinition } = require('/nodes.js');
const def = PatternRasterNodeDefinition.createDefault();
EnclosureRasterNode
A raster node that wraps raster content in an enclosure. Less commonly used directly — typically created through other SDK operations.
// Snippet — assumes doc is defined
const { EnclosureRasterNodeDefinition } = require('/nodes.js');
const def = EnclosureRasterNodeDefinition.createDefault();
All three use standard node patterns: doc.addNode(def), AddChildNodesCommandBuilder for insertion targeting, and access to rasterInterface / storyInterface where applicable.
MeasurementNode
A measurement annotation — a dimension line between two points. Create it with MeasurementNodeDefinition.create(start, end, factor, unitType) and add it via builder.addMeasurementNode().
'use strict';
const { MeasurementNodeDefinition } = require('/nodes.js');
const { AddChildNodesCommandBuilder } = require('/commands.js');
const { UnitType } = require('/units.js');
const { Document } = require('/document.js');
const doc = Document.current;
if (doc) {
const def = MeasurementNodeDefinition.create(
{ x: 100, y: 100 }, // start point
{ x: 400, y: 100 }, // end point
1.0, // scale factor
UnitType.Pixel // measurement unit
);
const builder = AddChildNodesCommandBuilder.create();
builder.addMeasurementNode(def);
doc.executeCommand(builder.createCommand(true));
console.log('Measurement added.');
}
Companion DocumentCommand factories configure measurement display globally: createSetMeasurementUnits(unitType), createSetMeasurementPrecision(useDocumentPrecision, decimalPlaces), createSetMeasurementAnnotationOffset(node, offset), and createSetMeasurementShowEndpointMarkers(show). Note there is no isMeasurementNode type guard.
DevelopNode
Represents a RAW image in the Develop persona. Read its state via isDevelopNode, .parameters (a DevelopParameters), and .sourceIsRaw; push changes back with createSetDevelopParameters(sel, developParameters). See Lesson 16 for the full develop workflow.
Exercises
Exercise 14.1 — Pixel inverter
Select a raster node. Read every pixel and write back its inverted colour (255-r, 255-g, 255-b). Use PixelReaderWriterRGBA8.
Exercise 14.2 — Tag all selected nodes
Build a dialog asking for a key and a value. Apply that tag to every selected node using createSetTagValueForKey.
Exercise 14.3 — Macro recorder UI
Build a dialog with buttons to start recording, stop recording, replay, and clear a macro. Wire each to the corresponding DocumentCommand.
Exercise 14.4 — Raster selection from polygon
Create a rectangular raster selection programmatically using createSetRasterSelectionFromPolygon (replaces createRasterSelectPolygon), then invert it with createRasterInvertSelection.
Exercise 14.5 — Image asset catalogue
Walk every ImageNode in the document and log its layer name, source file path, original DPI, and file size using imageResourceInterface. Flag any image below 150 DPI with a [LOW-RES] marker.
SDK modules used in this lesson
/pixelaccessor— All PixelReader* and PixelReaderWriter* classes/taginterface—TagInterface,PredefinedTagKey/rasterbrush—RasterBrush,BrushDynamic/imageresourceinterface—ImageResourceInterface/pictureframeinterface—PictureFrameInterface/rasterselection—RasterSelection/timers—Timer/units—UnitType,UnitValue,UnitValueConverter/hatch—HatchPattern,HatchLine/drawingscale—DrawingScale/marginsinterface—MarginsInterface/documentproperties—DocumentProperties/commands— updatedDocumentCommand,AddChildNodesCommandBuilder/nodes—TableTextNodeDefinition,PatternRasterNodeDefinition,EnclosureRasterNodeDefinition,MeasurementNodeDefinition,DevelopNode/os—OS/environment—Environment,EnvironmentPermission/logging—LogFile,LogLevel/configuration—ConfigurationItem,ConfigurationValueType/inspect—inspect,format,patchConsole,unpatchConsole/pathbrush—PathBrush,CornerStrategy,PathBrushDynamicControllerType
See also
Example scripts
14a-pixel-inverter.js
Download 14a-pixel-inverter.js
'use strict';
// Example 13a — Pixel inverter
// Lesson 13: New modules
//
// Inverts every pixel in the selected raster node.
// Demonstrates the PixelReaderWriterRGBA8 API.
//
// Open the image as a document (File > Open), not as a placed image on a page.
// Select the layer and run.
const { Document } = require('/document.js');
const { DocumentCommand } = require('/commands.js');
const { PixelReaderWriterRGBA8 } = require('/pixelaccessor.js');
const doc = Document.current;
if (!doc) { console.log('No document open.'); }
else {
const node = doc.selection.nodes.filter(n => n.isRasterNode || n.isImageNode).first;
if (!node) {
console.log('Select a raster or image node and run again.');
} else {
const ri = node.rasterInterface;
const w = ri.width;
const h = ri.height;
if (String(ri.format) !== 'RGBA8') {
console.log('Format is', ri.format, '— this script requires RGBA8. Open an RGBA8 image and run again.');
} else {
const bitmap = ri.createCompatibleBitmap(true); // copy contents
const rw = PixelReaderWriterRGBA8.create(bitmap);
console.log(`Inverting ${w}×${h} pixels...`);
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const px = rw.readPixel(x, y);
rw.writePixel(x, y, {
r: 255 - px.r,
g: 255 - px.g,
b: 255 - px.b,
alpha: px.alpha
});
}
}
doc.executeCommand(DocumentCommand.createReplaceBitmap(doc.selection, bitmap));
console.log('Done — pixels inverted.');
}
}
}
14b-resource-inspector.js
Download 14b-resource-inspector.js
'use strict';
// Example 13b — Resource inspector
// Lesson 13: Special Modules
//
// Reports image resource metadata, picture-frame state, blend options,
// and DPI-aware unit conversions for the selected node.
const { Document } = require('/document.js');
const { UnitType } = require('/units.js');
const doc = Document.current;
const node = doc?.selection.nodes.first;
if (!node) { console.log('Select a node.'); }
else {
const iri = node.imageResourceInterface;
if (iri) {
console.log('Image file: ', iri.imageFilePath ?? '(embedded)');
console.log('File size: ', iri.imageFileSize, 'bytes');
console.log('Original DPI: ', iri.originalDPI);
console.log('File type: ', iri.fileTypeName);
} else {
console.log('Not a placed image (no imageResourceInterface).');
}
const pfi = node.pictureFrameInterface;
if (pfi) {
console.log('Picture frame enabled:', pfi.enabled);
console.log('Has frame contents: ', pfi.hasFrameContents);
if (pfi.hasFrameContents) {
console.log('Contents:', pfi.frameContents?.descriptionInterface?.description ?? '(unnamed)');
}
}
const opts = node.blendModeInterface?.blendOptions;
if (opts) {
console.log('Blend gamma:', opts.gamma);
}
const pxPerPt = doc.unitValueConverter.getConversionFactor(UnitType.Point, UnitType.Pixel);
console.log('1 pt =', pxPerPt, 'px at', doc.dpi, 'dpi');
}