WhimTex: agent API v1
Use this reference for document files and batch operations. For a document already open in WhimTex, use LiveAgentAPI so edits participate in that window’s revision and Undo workflow. For clipboard content without a connected Editor, use the self-contained authoring contract.
| Task | Start here |
|---|---|
| Connect and discover commands | Connecting |
| Read or write document/layer JSON | Unified JSON documents |
| Change settings or build a layer stack | Batch contract |
| Validate, recover or retry | Validation, Undo and recovery |
| Work directly with C# layer objects | Layer identity and behaviour |
On this page
- WhimTex: agent API v1
- Connecting
- Unified JSON documents
- Shared editing operations
- Layer identity and behaviour
- Generated image → compositor
- Batch contract
- Validation, Undo and recovery
- Verification
Connecting
Use a running Unity Editor in Edit Mode. The plugin’s optional adapter supports
com.unity.pipeline 0.5.0-exp.1 or later and registers these commands after scripts compile.
The core API has no dependency on Pipeline. JSON parsing uses Unity’s Newtonsoft Json package.
When copying the plugin into Assets instead of using UPM, ensure its declared package dependencies
are already installed; an Assets-local package.json does not install dependencies automatically.
unity status --project-path 'D:/Projects/MyGame' --format json
unity command --query whimtex --project-path 'D:/Projects/MyGame' --format json
unity command whimtex_describe --project-path 'D:/Projects/MyGame' --format json
If commands are absent, check whether Pipeline is installed and the plugin is compiled. Follow the
project’s rules for compilation or installation; do not trigger them automatically when prohibited.
No API command calls AssetDatabase.Refresh, requests script compilation, enters Play Mode or opens a scene.
Import/save commands do import the specific image or compositor asset they write.
| Command | Parameters | Result |
|---|---|---|
whimtex_describe |
none | Protocol, operations, enums, limits |
whimtex_assistant_sessions |
none | Discover open document sessions and focus history |
whimtex_assistant_begin |
requestId; optional capture/placement arguments |
Reserve a new layer and capture context in an open document; Assistant contract |
whimtex_assistant_lock |
requestId, layerId; optional sessionId, expectedRevision |
Lock existing-layer content for generation; edit contract |
whimtex_assistant_live |
Absolute requestPath |
Inspect, capture, preview, deliver, render or cancel an open-document job; request schemas |
whimtex_document_inspect |
assetPath |
Document revision, stable IDs, hierarchy and settings |
whimtex_image_import |
sourcePath, assetPath |
Imported texture path, GUID, dimensions |
whimtex_batch_execute |
requestPath |
Batch result, created IDs, updated document |
whimtex_document_render |
assetPath, outputPath, optional maxSize=1024, overwrite=false |
Absolute PNG path and dimensions |
whimtex_storage_inspect |
assetPath (.tiff) |
Metadata-only block catalog, sizes and disk revision |
whimtex_document_validate |
assetPath, optional render=false |
Structure, limits, references and Shader FX validation; no save |
whimtex_fx_compile |
Exactly one of presetPath or source; optional includeBasePath |
Compile a preset or raw HLSL in Unity; return diagnostics without editing a document |
whimtex_fx_catalog |
Optional query, presetId |
Discover installed presets; inspect defaults, bounds and enum choices |
whimtex_assistant_execute |
Absolute requestPath |
Revision-checked shared operations in an open window; one Undo step, no save |
whimtex_render_probe |
Absolute requestPath |
Render composite/layer/FX stages and channels; report linear ranges |
whimtex_document_status |
assetPath |
Disk revision, GUID, importer, dirty/live/lock and staged recovery state |
whimtex_document_compare |
leftPath, rightPath, optional render, maxSize |
Compare model, TIFF storage and optional rendered pixels |
whimtex_document_recover |
sourcePath, destinationPath |
Recover a staged TIFF into a new document |
whimtex_document_export |
assetPath, outputPath, optional maxSize, overwrite |
Export a flattened document to PNG/JPEG/TGA/EXR |
whimtex_headless_live |
requestPath |
In-memory TIFF preview/commit session without a window; active state does not survive reload |
Pass --project-path and --format json on every command. The API object is nested inside the
CLI/Pipeline response: check its apiVersion and success as well as transport success/exit code.
An API validation error can arrive through a successful transport. errorCode and error describe it;
failedOperation, when present, is zero-based (-1 means batch/save level).
For whimtex_document_validate, a readable document may still return success:true with valid:false and
an errors array; check both fields before using it as an input for another batch.
Compile an FX preset without applying it
Use whimtex_fx_compile to check code through Unity’s shader compiler before adding it to a
document. Supply exactly one input:
presetPathfor a.hlslfile insideAssets,Packagesor the configured Shader FX user-preset folder. Presets must start with a valid// @whimtex-effect Category/Nameheader.sourcefor a raw HLSL string implementing WhimTex’sfloat4 ApplyFX(float2 uv, float4 color)function. It may include// @paramdeclarations and does not need the preset header. Raw source with relative#includepaths must also supplyincludeBasePath, pointing to a base directory inAssetsorPackages, or to an existing HLSL file whose directory is used as the base. Absolute directories insideAssets, a package, or the configured user-preset folder are also accepted. If the source has no relative includes, omitincludeBasePath. Unity,Assets/...andPackages/...includes need no base path.
Compilation creates only a temporary shader and material; it does not create an asset, modify source, change a document or require the WhimTex window. Unity Editor must be connected to the agent command bridge.
The API call’s success means the diagnostic request completed. Check compiled separately:
compiler warnings are returned in warnings and do not make compiled false; compilation and source
validation failures return compiled:false with entries in errors. diagnostics contains all
messages, with file and line when Unity provides a source location.
unity command whimtex_fx_compile --presetPath 'Packages/com.dcfapixels.whimtex/src/FXPresets/Halftone.hlsl' --project-path 'D:/Projects/MyGame' --format json
$fx = @'
// @param color _Tint = (1, 1, 1, 1)
// @param color _Accent = #FF0000FF
// @param gradient _Ramp = #FF0000FF -> #0000FF
float4 ApplyFX(float2 uv, float4 color) { return color * _Tint * _Accent * _Ramp_Sample(uv.x); }
'@
unity command whimtex_fx_compile --source $fx --project-path 'D:/Projects/MyGame' --format json
whimtex_describe also returns agentModes and storagePolicy. New documents use TIFF or unified JSON:
whimtex_batch_execute creates and saves *.tiff or *.json. Document paths ending in .asset return invalid_path, including read and dry-run requests. Convert old documents to TIFF in WhimTex 0.12.5 before upgrading.
Direct C# entry points, all on Unity’s main thread, return a JSON string:
WhimTexApi.Describe();
WhimTexApi.Inspect("Assets/Art/Icon.tiff");
WhimTexApi.ExecuteJson(requestJson);
WhimTexApi.ExecuteFile(absoluteRequestPath);
WhimTexApi.ImportImage(absolutePngPath, "Assets/Art/Source.png");
WhimTexApi.Render("Assets/Art/Icon.tiff", "Temp/WhimTex/icon.png", 1024, false);
WhimTexApi.InspectStorage("Assets/Art/Icon.tiff");
WhimTexApi.Validate("Assets/Art/Icon.tiff", false);
WhimTexApi.Status("Assets/Art/Icon.tiff");
WhimTexApi.Compare("Assets/Art/Old.tiff", "Assets/Art/New.tiff", true, 1024);
WhimTexApi.Recover("Assets/Art/Wall.tiff.whimtex-tmp", "Assets/Art/Wall-recovered.tiff");
WhimTexApi.Export("Assets/Art/Icon.tiff", "Temp/WhimTex/icon.jpg", 0, true);
WhimTexApi.TiffLiveFile(absoluteRequestPath);
WhimTexApi.AssistantExecuteFile(absoluteRequestPath);
WhimTexApi.FxCatalog("Color");
WhimTexApi.RenderProbeFile(absoluteRequestPath);
The full namespace is DCFApixels.WhimTex. With an existing C# eval bridge, call these methods
instead of installing Pipeline solely for this tool. Send a request file to avoid shell-escaping JSON.
Independent TIFF Live Update
whimtex_headless_live is the persistent, window-independent variant of Live Update. It keeps a
transient model between requests and supports begin, status, preview, render, complete
and cancel:
{"apiVersion":1,"op":"begin","sessionId":"wall-live","assetPath":"Assets/Art/Wall.tiff","expectedRevision":"<inspect revision>"}
{"apiVersion":1,"op":"preview","sessionId":"wall-live","requestId":"preview-1","operations":[{"op":"add","type":"color","as":"overlay","settings":{"name":"Overlay","color":[1,0.2,0.1,1]}}]}
{"apiVersion":1,"op":"render","sessionId":"wall-live","requestId":"render-1","outputPath":"Temp/WhimTex/wall-preview.png","overwrite":true}
{"apiVersion":1,"op":"complete","sessionId":"wall-live","operations":[]}
Nonempty operations in preview, render or complete rebuild the candidate from the snapshot
captured at begin; they do not accumulate edits from earlier previews. Send the full candidate list.
Omitted operations or operations:[] keep the current working model; they do not reset it.
render without operations is read-only, but with nonempty operations it also replaces the candidate.
complete verifies the original disk revision before one atomic save. If another writer changed the
TIFF, it returns revision_conflict and the session remains available for status or cancel.
The existing whimtex_assistant_live remains the open-window API.
Headless session lifecycle and retries
- Operations are
begin,list,status,preview,render,complete,cancel.listaccepts an optional TIFFassetPathfilter; other calls address an explicitsessionId. beginrequires a unique 1..128-charactersessionId. At most eight sessions may be active; a TIFF already open in a WhimTex window or another Headless session is rejected. Existing documents require the document revision frominspect, not the disk SHA-256 fromstatus. For creation usecreate:true, optional width/height (default 512, each 1..16384, at most 16,777,216 pixels), and omitexpectedRevision. The destination must not exist.beginis not idempotent: after a timeout, uselist/status, not another blindbegin. Preview replay remembers only the last nonempty operation request with arequestId. Repeating that ID requires identical arguments; changing them returnsrequest_conflict. Replayed additions may receive new IDs. Use@aliasesinside the complete operation list, not IDs of layers created by an earlier candidate.- Active models exist only in memory and are lost on domain reload or Editor exit. No user Undo
history is provided. Only the last 32 successful
complete/cancelreceipts are persisted for identical terminal retries; these receipts do not restore an active session. Reusing an ID in a newbegininvalidates its old receipt.statusaddresses active sessions, not terminal receipts. completewithout new operations saves the current candidate and closes the session;canceldiscards it without saving. On failed completion inspect disk/status and the still-active session before retrying: a post-commit import error is not proof that the TIFF was unchanged. Headless sessions do not publish the window’s GPU Live Update output.
Unified JSON documents
whimtex_document_json takes an absolute requestPath. Equivalent C# methods are
WhimTexApi.DocumentJsonFile(path) and WhimTexApi.DocumentJson(requestJson).
Content uses the shared document format; commands are only an operation envelope.
There is no required kind: the same content can be opened, written, inserted or used for an explicit
layer replacement. Root kind is rejected; 0.12.5 writers already omit it.
The document object and its fields are optional. Open/write use version-1 defaults for missing
settings (512 × 512 canvas). Insert/replace use destination dimensions for each omitted source axis,
without resizing or changing destination output settings. Standalone validate uses format defaults.
Writers always include both canvas dimensions, including in Compact mode, to preserve placement context.
{"apiVersion":1,"action":"serialize","assetPath":"Assets/Art/Icon.tiff","mode":"FullOptimized","allowDrawingOmission":false}
| Action | Inputs and result |
|---|---|
serialize |
assetPath, optional layerIds array, mode, allowDrawingOmission. Returns json containing all or selected layers plus warnings, in the same document format. |
export |
Same options without layerIds, plus a new destinationPath ending in .json. Does not modify the source. |
validate |
Exactly one json object or sourcePath; optional compile (default false). Detached parsing, no save. |
write |
json or sourcePath, destination TIFF or JSON assetPath, optional save (default true). Accepts whole-document or selected-layer exports; existing files require expectedRevision, including when save:false. |
insert |
json or sourcePath, existing assetPath, expectedRevision, optional save (default true). Adds remapped roots at the top without changing output settings. |
replace |
Same as insert plus layerId; exactly one incoming root. Keeps the target wrapper’s ID, name, enabled state and placement in the stack. |
open |
assetPath of a JSON document. Opens/focuses its WhimTex window. |
mode is Full, FullOptimized (default), or Compact. Nonempty Drawing pixels require
allowDrawingOmission: true when serializing/exporting. Empty Drawing nodes remain. write, insert
and replace compile by default; compile:false only postpones compilation and does not make shader code trusted.
Failed FX compilation is reported in warnings; the JSON document still loads and retains the skipped
FX for repair. Structural JSON/reference errors still reject the request. write without mode preserves
the optional envelope writeMode (or uses FullOptimized when absent). Ordinary JSON saves retain that mode;
one-off exports do not change it. Insert/replace use the request’s mode (FullOptimized if omitted)
when saving to JSON. A TIFF destination still uses TIFF save validation and may reject broken FX;
the soft JSON storage rule is not a bypass for TIFF validation.
Path edits refuse a conflicting open document. For write, insert and replace, save:false returns
saved:false and the prospective snapshot, then discards the transient model. It neither creates nor
overwrites the destination or its importer metadata; existing-file revision checks still apply.
Insert/replace do not overwrite the destination’s dimensions, encoding, filtering or History.
Revisions include current Drawing pixels even for JSON-backed documents, including pending paint-surface
changes; this conflict-detection fingerprint does not imply that JSON can store Drawing pixels.
The regular batch, inspect and render APIs also support .json (including earlier .whimtex.json names). Independent Headless Live sessions
and TIFF storage diagnostics remain TIFF-specific. json is content, never an ExecuteJson request.
Status reports format:"whimtex.document" for JSON paths. JSON open reports success:false with
errorCode:"open_failed" and the load error when a recognized document cannot load; it does not show a modal
error dialog. An already open document is focused without reloading it.
Regular Validate returns JSON load warnings (including missing assets, omitted Drawing content and failed
FX) in warnings. Structural errors make valid:false; unavailable FX also fail the readiness check while
remaining loadable/editable. Check both the response success and valid, not just transport success.
JSON document fields use strict types, named enums and finite representable numbers, with field paths in errors.
They retain native storage semantics rather than imposing every UI/patch range; see the shared format reference.
For a browser AI without a Unity connection, use the separate clipboard JSON/HLSL contract. Clipboard paste does not execute the operations described on this page.
The installed package also contains Samples~/AgentTextures/manifest.json: 38 procedural reference
recipes with individual PNG previews, descriptions and tags. The longest canvas axis is 256 pixels;
rectangular samples retain their aspect ratio. Read that folder’s README before reuse. There are no
bundled TIFF duplicates or overview atlas. The .whimtex.json files are complete editable documents,
not requests for ExecuteJson: use the shared JSON API above, open a copy in an authorized folder,
or paste their layers. Do not modify bundled references in place.
Output encoding is stored in each JSON document. The three packed-data previews store raw linear values and
must be imported with sRGB disabled; generic diagnostic PNG renders instead use sRGB preview encoding.
WhimTex is installed as com.dcfapixels.whimtex, its namespace is DCFApixels.WhimTex and its
assemblies are DCFApixels.WhimTex*. File compatibility covers TIFF/JSON documents and presets
saved by 0.12.5, using their canonical type and field names. There is no historical-name scan through
Unity migration attributes. Convert older files with 0.12.5 before upgrading; compositor .asset
documents must be saved as TIFF. Public C#/agent aliases and old window layouts are not retained.
Use WhimTexApi and the whimtex_* commands; the JSON command contract remains v1.
User settings are outside file compatibility and may reset after upgrades, without migration.
Canvas tool preferences use DCFApixels.WhimTex.Canvas.*; appearance preferences use
DCFApixels.WhimTex.CanvasView.*. The default library is the user’s local application-data
DCFApixels/WhimTex/Presets folder; historical folders are not searched automatically.
Select an existing library explicitly in User Settings. Changing/resetting the path does not move,
rewrite or delete preset files; an existing custom folder setting is still read directly.
The API edits the same model and uses the same renderer, brush and save path as the window.
For reservations, generation and selected-region edits in an open (possibly unsaved) document,
use the live editing API. The path-based batch contract below remains unchanged.
No WhimTex window or active selection is required. New agent documents may use a TIFF or JSON
assetPath, such as Assets/Art/Icon.tiff or Assets/Art/Icon.json. Old .asset documents are unsupported; convert them in WhimTex 0.12.5 before upgrading.
TIFF batches use a transient WhimTexDocumentBuild and the common TIFF writer; they do not create
or select a WhimTex window.
| Editing mode | Working state | Persistence and Undo |
|---|---|---|
| Batch | Independent copy for one request | save:true writes TIFF or JSON according to the path; save:false discards edits after returning. No user Undo of the file |
| Headless Live | Independent candidate between requests | complete saves; cancel/reload discard. No user Undo |
| Assistant | The user’s open document | Undo in the window; no automatic save |
Output encoding (C#)
WhimTexDocumentFile.GetOutputSrgb(document) reads the pending output encoding;
SetOutputSrgb(document, bool) changes it on the Editor main thread and marks the document changed.
It does not write the TIFF or importer: call Save to apply it together with other pending edits.
TextureCompositor.outputSrgb defaults to true. TIFF opening initializes it from the importer/carrier;
JSON restores document.outputSrgb, or the format default if omitted.
The UI supplies Undo; C# callers manage their own Undo records.
Inspector Apply instead queues a conversion of the saved model after import, without saving current
layer edits, then synchronizes the open document’s encoding. This external reimport stops Live Update.
Float32 output always remains Linear; alpha is unchanged. This is not a working-space or source-pixel
conversion. These are C# methods, not batch operation names. The unified JSON storage field
document.outputSrgb is separate from this importer-conversion workflow.
Path-based inspection/rendering reads the disk document. To inspect unsaved window changes use Assistant; to inspect an unsaved Headless candidate use its session. These states are not interchangeable.
The window’s optional Live Update publishes preview pixels to the existing output texture on the GPU
without changing its asset reference or CPU pixel data. It is not an API autosave mode: save the open
document through its window, and use Assistant rendering to obtain current pixels rather than reading OutputTexture.GetPixels() during live
preview. Disabling live output restores the saved image; preview EV, channel display and Post FX are excluded.
Shared editing operations
The operations below work in whimtex_batch_execute, the operations array of
whimtex_headless_live preview/render/complete requests, and whimtex_assistant_execute.
Headless preview retains its existing replay from baseline semantics: send the complete
candidate operation list, not an incremental patch to the previous preview.
For the open window, inspect with whimtex_assistant_live (op:inspect) first, then call
whimtex_assistant_execute with an absolute requestPath containing:
{"apiVersion":1,"sessionId":"OPEN-SESSION","expectedRevision":"DOCUMENT-REVISION","dryRun":false,
"operations":[{"op":"fx","layer":"LAYER-ID","edits":[
{"op":"set","index":0,"parameters":{"_Opacity":0.5},"enabled":true}
]}]}
This synchronous batch forms one Undo step and does not save. It requires an idle document
without pending reservations/edit locks; it never cancels another job. After a timeout inspect
before retrying: operations such as duplicate and paint are not idempotent. A stale revision
is rejected. Use the reservation/lock workflow for longer generation tasks; its changes.fx
schema remains separate from this batch’s edits schema. dryRun validates on an independent
copy; it does not compile new HLSL, render or calculate healing.
FX edits and presets
whimtex_fx_catalog accepts an optional query category/name substring. It discovers presets
in Assets, installed packages and the user preset library. Each result has id, path, name,
kind and error. Pass an exact returned presetId to obtain effect with code and parameter
types, current defaults, hard/soft bounds and enum options. Use IDs, not display names; invalid
presets remain visible with an error. This command does not insert anything into a document.
{"op":"fx","layer":"ID-or-@alias","edits":[...]} supports:
| Edit | Fields and behavior |
|---|---|
add |
Exactly one of code or presetId; optional insertion index (default append), parameters, enabled. Raw code supports optional includeBasePath, required for relative includes |
replace |
Existing index, otherwise the same fields as add |
set |
Existing index, optional parameters and enabled; preserves code and does not recompile HLSL |
remove |
Existing index |
move |
Existing index and toIndex, the final index after removal |
copy |
sourceLayer ID or alias, sourceIndex; optional destination insertion index, parameters, enabled. Independent copy in the destination layer |
apply |
Existing index; bake that FX and all preceding entries, including disabled entries (removed without rendering). Later FX remain |
applyAll |
Bake the complete nonempty stack |
parameters is a name/value object, not a replacement list. Types are inferred from existing
declarations; unknown names, invalid enum values and hard-range violations fail. Use numbers for
enums, booleans for bools, RGBA arrays for colors, component arrays for vectors and the existing
live FX value formats for textures, gradients, curves and Transform2D. Unmentioned values remain.
Indices refer to the latest inspected stack and change after each edit. Limits: 32 edits per
operation, 32 FX per layer, 128 parameters per effect, 65,536 characters of raw HLSL.
Set/copy use independent document-owned values rather than modifying a shared external asset. Project/package HLSL presets retain their source link; user-library files are embedded. Shader FX supports all edits; Material entries support remove/move/baking, not parameter editing or copying.
Prefer presetId for existing built-in/project effects, with parameter overrides for the desired look.
Do not copy their unchanged HLSL into code: that creates an independent inline effect and loses
the catalog link. Use set for parameter-only changes. Raw code is for custom algorithms or
intentionally independent variants, not the default way to reuse a preset.
The built-in Color/Gradient Map samples full gradient RGBA from _SourceChannel: Luminance=0
(default, unchanged), R=1, G=2, B=3, Alpha=4. Luminance uses nonnegative linear RGB weights
0.2126/0.7152/0.0722; it and individual RGB sources clamp to 0..1 and use sRGB encoding before
lookup. Alpha clamps to 0..1 without sRGB conversion. _Reverse precedes _Mapping, then the
result samples the gradient. This selects one source, not independent per-channel remapping. Output alpha is
sourceAlpha * lerp(1, gradientAlpha, saturate(_Opacity)); an opaque gradient preserves source alpha,
zero strength bypasses the effect, and transparent input stays transparent. _Mapping and _Reverse
affect the lookup for both RGB and alpha.
Applying to a non-Drawing layer requires allowRasterize:true inside the apply edit.
This consents to rasterization, including flattening a group. Logical transforms remain editable.
Shader Processor captures its backdrop and maps Normal to Overwrite with its original opacity
blending; lower layers remain separate. See baking details.
Layer structure
{"op":"delete","layer":"ID"}removes a layer or group subtree, not its external source assets.{"op":"duplicate","layer":"ID","as":"copy"}inserts an independent copy using normal layer duplication, including owned pixels/FX and internal reference remapping.{"op":"merge","layer":"ID","others":["OTHER-ID"],"keepSources":false,"as":"merged"}uses normal merge rendering and returns the new Drawing layer.keepSources:trueretains originals. Merge is explicitly rasterizing; its composite placement follows the normal merge operation.{"op":"convertToDrawing","layer":"ID","as":"drawing"}uses Keep Transform conversion, preserving ordinary layer FX. Groups are flattened using the normal group conversion rules. Shader Processor must use FX apply/applyAll to capture its lower input instead.
as is optional for duplicate/merge/conversion; subsequent operations may use @alias.
Removed aliases cannot address detached layers. Existing dependency validation still applies:
do not delete an input while leaving an invalid explicit target reference.
Blur, smudge and healing strokes
These tools require Drawing; convert explicitly when appropriate. Points are canvas pixels with a
top-left origin. They do not implicitly use the window’s area selection or brush settings.
tiled:true wraps the canvas boundary; repeating layer transforms are not supported (Clip and
Unbounded are accepted). Size is 1..512 px, hardness 0..1, up to 4096 points.
{"op":"blurStroke","layer":"DRAWING-ID","points":[[10,20],[40,20]],
"size":24,"hardness":0.5,"strength":0.7,"flow":1,"source":"CurrentAndBelow","tiled":true}
Blur source is CurrentLayer (default), CurrentAndBelow (includes current), or AllLayers.
It is frozen at stroke start. Strength and flow are each 0..1, default 1. Work is limited to
67,108,864 canvas-pixel × input-point passes; simplify a path rather than repeating thousands of points.
{"op":"smudgeStroke","layer":"DRAWING-ID","points":[[10,20],[40,20]],
"size":24,"hardness":0.8,"strength":0.8,"flow":1,"mixing":0.25,"source":"CurrentLayer","tiled":true}
Smudge carries existing RGBA pixels along the path, without adding a painting color.
One point alone does not paint. Strength (0..1, default 0.8) scales transport and controls
retention of the picked-up patch; flow (0..1, default 1) scales each dab independently.
Mixing (0..1, default 0.25) couples two mechanisms within each dab: deformation is
scaled by 1 - mixing, while carried-color deposition is scaled by mixing.
At 0, only a coordinate map is advected; colors are reconstructed from a mipmapped,
immutable stroke-start image, so repeated movement does not repeatedly filter RGB.
Partial Mixing advects positive accumulated paint contributions and original-image
weights, then deposits and picks up from their shared result. It is not interpolation
between finished strokes. No contrast compensation or signed color residuals are used.
At 1, only the carried-color mechanism is active.
Below full Strength, the carry is refreshed after deposition from the painted result,
including mask, selection and Flow: carry = lerp(paintedPatch, previousCarry, strength).
At Strength 1, the initial captured patch remains unchanged throughout the stroke;
Hardness and Flow affect the dab, not this retention. No foreground color is added;
larger Mixing intentionally produces cumulative color blending.
Sources match Blur. CurrentLayer samples
raw Drawing pixels; visible-stack sources freeze other layers/FX at stroke start
and feed back this stroke’s deformations and deposits. Writes affect only the selected Drawing.
Spacing is 2.5% of the tip diameter, at least one canvas pixel, and follows canvas distance
rather than input-point frequency. Hard tips have a pixel-wide antialiased boundary. Color pickup and feedback
stay on the source pixel grid, separately from the smoothly positioned round mask:
CurrentLayer uses native Drawing resolution; visible-stack sources use canvas resolution.
At full Strength, only deposition reconstructs the frozen patch at fractional pointer
positions; the unchanged carry is never replaced by that reconstruction.
The accumulator preserves individual pixels rather than reducing large tips to 1024×1024.
Transformed native footprints may be rectangular and grow during a projective stroke without
rescaling carried pixels. Up to 32,768 generated stamps are accepted, with a 268,435,456
native tip-pixel × stamp budget. Dry run checks the padded source-space footprint at path
vertices. Each carry buffer is limited to 18,874,368 pixels and the device’s maximum texture
dimensions; a footprint crossing the inverse projection horizon is rejected rather than
downsampled. Use a smaller tip or adjust the layer transform if these limits are exceeded.
Deformation snapshots and the growing stroke-history region are also limited to
18,874,368 native pixels. History is cropped to the touched area, with tile-aligned
growth; rendering operates on dab bounds rather than running full-canvas passes per dab.
{"op":"healStroke","layer":"DRAWING-ID","points":[[10,20],[40,20]],
"size":24,"hardness":0.8,"source":"CurrentLayer","search":64,
"quality":"Balanced","seed":1,"transparentOnly":false,"tiled":true}
Healing sources are CurrentLayer (raw pixels before FX) and CurrentAndBelow. Search is 8..512
canvas pixels; quality is Fast/Balanced/High. Instead of points, maskPath accepts an imported
project/package texture mapped over the canvas: linear red is repair coverage (white repairs).
Use linear-data import settings for an exact grayscale mask. Alpha is not used as coverage.
The API uses the same buffered stroke mask, tiled recentering and fill algorithm as the tool.
Healing currently limits the full canvas to 1,048,576 pixels and cancels computation after
20 seconds; cancellation fails the operation without applying a patch. It is synchronous,
not a background job. All three tools limit total generated stroke stamps to 32,768.
Diagnostic rendering
whimtex_render_probe takes an absolute requestPath. C# equivalents are
WhimTexApi.RenderProbeJson/File, AssistantExecuteJson/File and FxCatalog(query, presetId).
{"apiVersion":1,"assistantSessionId":"OPEN-SESSION","layer":"LAYER-ID",
"stage":"afterFx","index":0,"channel":"a","maxSize":1024}
Choose exactly one source: TIFF assetPath, assistantSessionId, or headlessSessionId.
Stages are composite (no layer/index), layer (all its FX), beforeFx and afterFx (layer + index).
Layer/FX stages capture the canvas-sized input pipeline before outer opacity, blending, swizzle
and clipping; a Shader Processor uses its actual stack-position backdrop. They are not a solo
view of the final composited layer. Disabled FX have identical before/after images.
Channels: rgba (default), r, g, b, a (opaque grayscale). maxSize is 1..4096, default 1024.
Optional outputPath must be a new Temp/WhimTex/*.png; otherwise a unique path is generated.
The response includes the PNG path, dimensions, full-resolution linear per-channel minima/maxima
and nonfinite-component count. PNG is a display preview, not lossless HDR data. Rendering uses an
independent document copy and never edits or saves the source.
Layer identity and behaviour
TextureCompositor.layers contains stable Layer objects. Common settings, GUIDs, FX references,
group compositing and children belong to Layer; only the type-specific LayerBehaviour is
polymorphic. Use layer.Behaviour is DrawingLayerBehaviour drawing to access owned pixels or
other behaviour-specific methods. layer.SetBehaviour(new NoiseLayerBehaviour()) replaces the
behaviour without replacing the layer or changing its common settings. Document changes still
need the normal Undo/MarkChanged workflow; use the JSON API for agent authoring.
A behaviour can belong to only one layer. Do not share one behaviour between wrappers. Swapping behaviours releases transient rendering resources but does not destroy the old Drawing texture: Undo or the caller may still own it. Finish/synchronize an active stroke before recording the structural Undo snapshot, and use the existing document removal or conversion workflow when the old owned assets should also be deleted. Replacing a populated group with a non-group is rejected; use the editor’s explicit conversion to Drawing or ungroup it first. Conversion and live-generation completion retain the layer wrapper.
Snapshots include behaviourMissing. A missing behaviour does not remove the layer’s name, GUID,
common settings or group children. It does not render. Its type is missing, or group when the
saved node still contains a group. Type-specific commands require an available behaviour.
The Layer Settings recovery action can transfer compatible saved behaviour fields; common fields
are retained directly, not reconstructed from missing-type metadata. Recovery matches a separate
behaviour identifier rather than list positions or names.
Earlier inheritance-based documents are intentionally incompatible. Keep their originals and use the earlier package revision to render/export them. No automatic colour, naming or repeat-mode migrations are applied to the new document model.
Generated image → compositor
- Generate a PNG/JPEG using the agent’s image tool, or use a user-provided local image.
- Import it to a new asset path. Import never overwrites; for an existing imported texture, skip this step.
unity command whimtex_image_import --sourcePath 'C:/Temp/generated.png' --assetPath 'Assets/Art/AgentIcon/source.png' --project-path 'D:/Projects/MyGame' --format json
The API copies only that file, preserves its dimensions up to the resource limit, disables texture compression/mipmaps and keeps non-power-of-two dimensions. It does not change existing source importers. PNG/JPEG only; the destination must use the same extension. No URLs or automatic image generation.
- Write a JSON batch outside
Assets, for exampleTemp/WhimTex/create.json:
{
"apiVersion": 1,
"assetPath": "Assets/Art/AgentIcon/Icon.tiff",
"create": true,
"width": 1024,
"height": 1024,
"save": true,
"operations": [
{"op": "add", "type": "group", "as": "art", "settings": {"name": "Artwork"}},
{
"op": "add", "type": "file", "as": "image", "parent": "@art",
"settings": {"source": "Assets/Art/AgentIcon/source.png", "filter": "Source"},
"transform": {"position": [20, -12], "rotation": 8, "tiling": "Clip"}
}
]
}
- Optionally run the same request with
dryRun:true, then change it to false to apply:
unity command whimtex_batch_execute --requestPath 'D:/Projects/MyGame/Temp/WhimTex/create.json' --project-path 'D:/Projects/MyGame' --format json
unity command whimtex_document_render --assetPath 'Assets/Art/AgentIcon/Icon.tiff' --outputPath 'Temp/WhimTex/icon-v1.png' --project-path 'D:/Projects/MyGame' --format json
- View the returned PNG. Revise the document if needed, using IDs/revision from the response or a new inspect. The TIFF is imported by Unity as the composite texture/Sprite; this temporary PNG is only for inspection.
An initially empty File layer automatically gets Original Aspect when assigned its first texture.
Inspection reports the effective decoded sourceSize used for File rendering and, separately,
importedSourceSize from Unity’s imported texture. For supported original-file formats these may
differ because of importer downscaling. Inspect may populate the original-file decode cache.
Assigning a different HDR-format source sets both colorRange and blendRange to HDR.
Explicit ranges in the same settings object override these defaults. Reassigning the same source,
assigning a non-HDR source or subsequent refreshes do not reset the ranges.
An explicit transform patch is applied after this fit. Setting scale:[1,1] explicitly therefore
stretches a non-square source to the full canvas; omit scale to preserve the initial aspect fit.
Batch contract
| Request field | Meaning |
|---|---|
apiVersion |
Required integer 1 |
assetPath |
Required project-relative Assets/.../*.tiff or Assets/.../*.json for documents |
create |
Default false. True creates a new document |
width, height |
Create only; integers, default 512 each, 1..16384 and at most 16,777,216 total pixels |
expectedRevision |
Required for existing documents; copy the latest persisted document revision from inspect/successful save. Omit entirely on create; null is rejected. Do not use a discarded save:false candidate’s revision |
save |
Default true. False executes on a temporary copy and returns a snapshot, then discards it. It does not change an open window or retain a session. New documents require true unless dryRun |
dryRun |
Default false. Validate the entire batch on a detached model, without strokes, rendering, saving or consuming real name counters |
operations |
Required array, at most 256; use [] to save/rebake without additional edits |
These fields describe an editing request, not a whimtex.document file. Batch JSON saves use
the document’s write mode and do not offer Drawing-omission permission: nonempty Drawing content
cannot be saved to JSON through this command. Use TIFF for pixels, or explicitly authorize omission
through the document JSON API when exporting a settings-only copy.
Unknown/duplicate fields, wrong JSON types, invalid enum names, non-finite numbers and out-of-range
values fail validation. Omitted patch fields are preserved; JSON null is not a general reset
instruction. Only explicitly documented fields such as brush tip:null accept it.
Property and enum names are case-sensitive. Do not pass Unity instance IDs, YAML file IDs or display names.
Each add can define as:"image". Later operations refer to it as layer:"@image", parent:"@image"
or target:"@image". Aliases are local to one batch, must be unique and cannot forward-reference.
Persistent layer IDs are returned per operation and in document.layers.
document.layers is flat, with parent and sibling index. Index zero is visually topmost.
settings contains editable values; hierarchy, target and transform have separate fields/operations.
gradientKeys contains separate color/alpha keys, interpolation, smoothness and midpoints. Rounded is built in; transition is not an accepted input field. Gradient input uses RGBA arrays and string enum names; it does not convert old color objects or numeric enums.
Rounded prioritizes smooth constant-region joins at full smoothness; values at interior held-boundary stops may be approximate. See the gradient contract for its independent RGB/alpha maps and limits.
Color and gradient input
Gradient inputs accept either an ordered stop array or the object form documented in the gradient contract; SDF, Noise and general-purpose gradients default to Perceptual. Explicit modes are preserved.
Color-picker preferences are not API input transforms: RGB/HSV entry mode, HDR input toggles, Channels and Preview EV do not modify supplied RGBA or gradient values. API/clipboard writes do not add or promote colors in document History. Color and gradient JSON still use numeric RGBA, not the picker’s HEX text syntax. Editing-operation and brush RGB limits remain -107..107 with alpha 0..1; the standalone gradient clipboard’s -65504..65504 range does not extend these API limits. Unified document JSON is a separate storage contract with native field types and representability checks, not these editing-operation ranges; see JSON format. HDR texture data is distinct from physical HDR monitor output; rendered PNG previews are SDR.
Operations
{"op":"add", "type":"drawing", "as":"ink", "parent":"@art", "index":0, "settings":{"name":"Ink"}}
{"op":"set", "layer":"@ink", "settings":{"opacity":0.6, "blend":"Multiply"}}
{"op":"transform", "layer":"@image", "transform":{"position":[24,-10], "rotation":15}}
{"op":"move", "layer":"@ink", "parent":"", "index":0}
{"op":"target", "layer":"@outline", "input":"Specific", "target":"@art"}
{"op":"target", "layer":"@outline", "input":"Previous"}
add: typesfile,drawing,group,color,gradient,noise,shape,outline,sdf,normalMap,blur,sharpen,makeSeamless,shaderProcessor. Optionalparentdefaults to root,indexto 0.settingsandtransformare optional patches.set: requireslayerandsettings.transform: requireslayerandtransform.move:indexis the insertion index after removal from the old container; omitted parent orparent:""moves to root. A group cannot move into itself or its descendants.target: effect layers (SDF/Outline/Normal Map/Blur/Sharpen/Make Seamless); default input Specific. Previous means the next sibling below the effect. AllBelow means the composited visible lower siblings in the same group, starting on transparency, including opacity/blending/FX; even Pass Through does not include the external backdrop. Previous and AllBelow forbid a target; Specific requires one. Example:{"op":"target","layer":"@effect","input":"AllBelow"}. The discovery fieldeffectInputModeslists all three modes. Empty AllBelow is transparent; cycles are rejected. Specific targets can be groups, but cannot create a dependency cycle.stroke: Drawing only, detailed below.
Layer settings
| Applies to | Supported keys |
|---|---|
| All | name (string), enabled / clippingMask (boolean), opacity (0..1), blend, colorRange / blendRange (Standard, HDR), swizzle (four channel names in output RGBA order) |
| Non-group | filter (Source, Point, Bilinear, Trilinear) |
| Group | compositing (PassThrough, Isolated); ranges are active only when isolated |
| File | source (already imported Texture2D path in Assets or Packages) |
| Color | color ([r,g,b,a], encoded RGB -107..107, alpha 0..1), fillMode (Color, UV, Pattern), fillPattern (partial settings below) |
| Drawing | brush (partial brush settings below) |
| Outline | color, metric, sourceChannel (Alpha default, Red, Green, Blue, Luminance), outlineWidth, outlineSoftness (0..16384), outlinePosition (Outside, Inside, Center), outlineOffset (-16384..16384), fillCenter (bool), fillColor ([r,g,b,a]) |
| SDF | metric, sourceChannel (Alpha, Red, Green, Blue, Luminance), threshold (integer 0..255), distancePosition (Outside, Inside, Center, Signed), encoding (Gradient default or LinearData), inverted (bool, both output modes), maxDistance (0..16384; zero = automatic), sourceOffset ([x,y], each -16384..16384 px), sourceEdges (Transparent, Clamp, Repeat, Mirror), contourOffset (-16384..16384 px; positive expands), insideDistance/outsideDistance (Signed only, 0..16384; 0 inherits maxDistance/auto), profile (FX curve string syntax, default linear) |
| Normal Map | normalMap: partial settings object described below |
| Noise | noise: partial procedural settings object described below |
| Shape | shape: partial settings object described below |
| Blur | blur: partial settings object; mode: Gaussian (default), Linear or Circular; Gaussian, motion |
| Sharpen | sharpen: algorithm (Gaussian/Adaptive), strength 0..4, radius 0..32 px, threshold/noiseReduction/haloSuppression 0..1, channelMode (RGB/Luminance), edges |
| Make Seamless | makeSeamless: { "mode": "OffsetBlend", "edgeWidth": 0.2, "offsetTransitionStart": -0.25 }; all four methods and parameters |
| Gradient, SDF | gradient: 1..64 {"time":0.0,"color":[1,1,1,1]} stops in strictly increasing time order, time 0..1 |
SDF also accepts encoding directly in settings: Gradient (default) or LinearData, reported by sdfEncodings in capabilities. Its default palette matches Noise: black at 0, white at 1, with Perceptual interpolation. Inverted remains available in both modes and reverses normalized distance before Profile and gradient sampling. LinearData outputs normalized distance after Inverted/Profile as raw linear RGB 0–1 with alpha 1, without gamma decoding or palette sampling. In Signed mode low values are inside and high values outside. Profile applies in both modes. Switching output preserves the palette and inversion setting; existing explicit gradient modes are retained. SDF has no useGradient field or ColorValues output. Document export encoding still applies normally; choose linear output when exporting numeric maps.
SDF operations write settings.gradient, not settings.sdf. Unified document/clipboard JSON
instead stores the native palette in behaviour.gradient on SDFLayerBehaviour.
Inspection reports the palette separately as gradientKeys; write that body’s value using gradient.
Both stop arrays and the shared gradient object are accepted, including separate alpha keys, HDR colors,
mode, smoothness, wrapMode and colorSpace. Supplying gradient replaces the whole palette but
does not select an output mode; set encoding:"Gradient" explicitly to enable it from LinearData.
SDF/Outline metric accepts EuclideanExact (default), EuclideanApproximate, Manhattan,
Chebyshev and EuclideanAntialiased. The latter interpolates threshold crossings between horizontal/vertical
neighboring samples and measures distance to the closest crossing point. It approximates the continuous contour
between those points; it does not treat a wide translucent transition as subpixel coverage.
threshold selects the SDF contour; Outline uses the fixed midpoint threshold (128) on its selected sourceChannel. In this mode a uniform source with no crossing has no border.
Existing metric numeric IDs 0–3 remain stable; EuclideanAntialiased is 4 and is reported by capabilities/inspect.
Outline filters both band edges, including at zero softness, and supports fractional widths. outlineOffset
translates the band in canvas pixels (negative inward, positive outward), without changing its width.
fillCenter defaults to false. When enabled, the region inside the band’s inner edge uses fillColor
(default white; same encoded RGB/alpha contract as color). Fill and border share complementary coverage,
so opaque colors do not create a translucent seam. Zero width removes only the border, not the enabled fill.
The source’s interior holes remain holes. This is an ordinary effect layer: place it below an explicitly targeted
source for a backing silhouette; above the source it can cover the original image.
PSD exports filled/offset/antialiased outlines as pixels rather than a native stroke style.
For SDF on a group, Alpha requests coverage only; Red/Green/Blue/Luminance request the group’s RGBA result.
Changing SDF Source Channel therefore also changes the group’s source-cache requirement.
Discover blend modes, ranges, group compositing and distance metrics with whimtex_describe.
Color-component blend names are Hue, Saturation, Color, and Luminosity, for layers,
isolated groups, and brushes. Standard uses non-separable luminosity/saturation blending in
sRGB blend space (luminosity weights 0.30/0.59/0.11, saturation max−min, luminosity-preserving
gamut clipping), not HSV channel replacement. HDR evaluates the same component substitutions
in linear light without gamut clipping. Alpha/opacity compositing is unchanged.
The UI groups choices independently of their stable enum values; JSON names do not change.
Groups default to PassThrough; set compositing:"Isolated" to apply their own blend mode and ranges.
Group opacity applies to the complete result, not separately to every child. Groups support transforms and FX. Child transforms are parent-local; canvas matrices compose from parent to child. The group’s frame is its own unit rectangle rather than the bounds of its children. The move operation preserves canvas placement when changing parents.
swizzle accepts R, G, B, A, 1-R, 1-G, 1-B, 1-A, 0, 1, R * A, G * A, B * A,
Luminance, Luminance * A as strings. Luminance is 0.2126 R + 0.7152 G + 0.0722 B in linear space;
the product uses the original alpha. ["1","1","1","Luminance"] converts brightness to alpha with white RGB.
Product names include spaces, matching Describe. All mappings read the original input RGBA:
["R * A","G * A","B * A","1"] multiplies RGB by the input alpha and sets output alpha to 1.
Selecting products does not change the compositor’s blending convention or implicitly change output alpha.
For example, "swizzle":["B","G","R","A"] exchanges red and blue;
["A","A","A","1"] displays alpha as opaque grayscale. The default is ["R","G","B","A"].
It runs after FX in linear working space and before Color Range and layer blending. Output alpha
remains bounded to 0..1. Source pixels and brush settings are unchanged.
A nonidentity group swizzle forces isolated rendering. A saved Pass Through group uses Normal
blending while swizzled, then resumes Pass Through when restored to identity, without FX and not participating in clipping. Explicitly isolated
groups retain their chosen blend mode. Describe lists swizzleChannels; Inspect includes each
layer’s swizzle, including groups.
Groups support FX on their combined children, before Swizzle and outer opacity/blending. A group with FX is automatically isolated; a saved Pass Through mode uses Normal blending until all FX are removed (unless Swizzle or clipping still requires isolation).
clippingMask defaults to false. Set it to true on a non-Processor layer or group to clip it to the
first non-clipping sibling below; consecutive clipped siblings share that base. The relationship
is positional, never crosses a parent group, and updates after moves. A hidden, transparent or
missing base hides the chain. The base’s alpha is preserved and its opacity is applied once.
Participating groups are isolated temporarily (configured PassThrough uses Normal).
Inspect reports settings.clippingMask, resolved clippingBaseId (null without a base), and
isolatedByClipping on groups. Toggling clipping does not edit source pixels or drawing strokes.
For an existing layer, use {"op":"set","layer":"<id>","settings":{"clippingMask":true}}
in a batch with the current expectedRevision. Place its base first when constructing a new
document, then add clipping layers above it. Clipped Overwrite replaces source-covered color
without erasing base alpha; outside clipping it retains full RGBA overwrite behavior.
Setting Drawing colorRange:"HDR" promotes storage. Standard does not downgrade it. The explicit operation
{"op":"compact","layer":"@drawing"} clamps/quantizes to 8-bit and switches to Standard
(undoable in Assistant, not a filesystem Undo for Batch/Headless).
Only use compact when the user asks to discard HDR precision. Inspect reports storageFormat.
Colors retain the encoded RGB convention; rendering and EXR/Texture2D output are linear HDR.
The render command writes a clamped PNG copy. See HDR behavior.
Unspecified settings and FX are preserved. The shared operations support Shader FX editing,
layer deletion, duplication, merging and conversion to Drawing; see shared editing operations.
Changing gradient geometry is not exposed by these batch operations.
Use enabled:false when hiding a layer is preferable to deleting it.
Resize a document
{"op":"resize","width":256,"height":128,"preserveLayout":true} is a document-level
operation shared by Batch, Headless Live and Assistant. Both integer dimensions are required
(1..16384; at most 16,777,216 pixels). It also works on an empty document; the operation result
has layerId:null and name:null, with the new dimensions in the document snapshot.
preserveLayout defaults to true: normalized layer placement is preserved throughout the group
hierarchy. Proportional resizing scales pixel-valued TRS positions; non-proportional resizing
stores equivalent normalized projective transforms to retain rotated/sheared placement.
Existing projective transforms remain unchanged. Set false to change only the canvas dimensions.
Layer IDs, source Drawing/File pixels, FX links, gradients and all other settings are retained;
this does not flatten or resample the editable source layers.
Pixel-valued effects are not scaled automatically: explicitly adjust blur radii, outline/SDF
distances, shape feather/strokes, pattern cell sizes and custom FX pixel parameters with set
or fx operations in the same batch. UV/normalized values should not be scaled. Compare rendered
previews: procedural sampling and antialiasing can differ at a smaller resolution.
Existing revision, dry-run, lock, save and Undo rules apply. Headless status reports the current
candidate size; a nonempty replay starts from its original baseline as usual.
Document revisions ignore rebuilt Shader FX compilation caches, but still include editable code,
parameters and activation state. Opening a detached TIFF restores its existing applied FX before
copying it; dry-run does not compile newly submitted shader code.
shaderProcessor processes the already-composited lower stack, with HDR ranges by default.
Normal + Opacity interpolates before/after without accumulating alpha twice. Pass Through includes
the external backdrop; isolated groups limit its scope. Processor is a clipping-chain boundary.
The batch API can create/reorder it, edit its settings and transform, and author its FX through op:"fx".
The live editing API can author inline Shader FX code and parameters
in open documents, including unsaved ones. No separate shader asset or special layer target is needed.
All WhimTex HLSL effects and brushes automatically include FastNoiseLite;
its noise and domain-warp functions need no explicit include.
Post FX is window-local presentation state and never changes API rendering, sampling or export.
Shape settings
Use type:"shape" and partial settings.shape updates. describe returns shapeDefaults
and shapeKinds; inspect includes all parameters. One layer contains one editable figure.
| Setting | Values |
|---|---|
kind |
Rectangle (default), Ellipse, Polygon, Star, Line |
fill, fillColor |
Boolean (default true), RGBA color (default white) |
stroke, strokeColor |
Boolean (default false), RGBA color (default black) |
strokeWidth |
0–8192 canvas pixels, inside the edge; default 2 |
feather |
0–8192 canvas pixels; total soft transition width; default 0 keeps the original antialiasing |
featherPosition |
Inside, Outside, Centered (default); fades inward, outward or equally across the contour, including both boundaries of a hollow stroke |
roundness |
0–1 uniform rectangle rounding shortcut; setting it assigns all four corners |
cornerRoundness |
Four 0–1 values, clockwise from top-left: TL, TR, BR, BL. Radius relative to the shorter half-extent |
linkCorners |
Boolean, default true; enables proportional corner edits in Properties |
sides |
3–32 polygon sides / star points; default 5 |
innerRadius |
0.01–1 star inner/outer radius ratio; default 0.5 |
API corner assignments are exact, regardless of linkCorners; when both rounding keys are present,
cornerRoundness overrides roundness. Inspect reports the resolved corners and the top-left value
as the scalar shortcut. UI displays rounding as 0–100%; API uses 0–1.
New shapes are centered with transform scale [0.5,0.5]. The untransformed bounds cover
the canvas: a 100×40 figure in a 512×256 document uses scale [0.1953125,0.15625].
Set position/rotation with the existing transform operation. Line is a capsule whose
length and thickness are its transformed width and height. Stroke width stays in canvas pixels.
Colors follow the existing encoded-color contract; set layer color/blend ranges to HDR when needed.
Inactive type-specific settings are retained when kind changes. SVG import/export is not implied.
Feather uses the procedural contour distance, not a blur pass. At large widths, thin strokes and small shapes can become faint or disappear with Inside/Centered. Outside can expand into nearby gaps. Feather is applied before layer FX, opacity and compositing; colors retain their RGB/HDR values.
{"op":"add","type":"shape","as":"badge","settings":{"shape":{
"kind":"Star","sides":5,"innerRadius":0.45,
"fillColor":[1,0.65,0.1,1],"stroke":true,"strokeWidth":3
}}}
Noise settings
Use type:"noise" with partial settings.noise updates. describe exposes noiseDefaults,
noiseTypes, noiseFractals, noiseCellularDistances, noiseCellularReturns, noiseWarps
and noiseEncodings, noiseDimensions, noisePeriodicAxes, noiseWhiteColors. inspect returns all generator parameters. No new operation or protocol version is required.
{"op":"add","type":"noise","as":"height","settings":{"noise":{
"noiseType":"OpenSimplex2","seed":1337,"scale":8,"offset":[0,0],
"fractal":"FBm","octaves":3,"lacunarity":2,"gain":0.5,"encoding":"LinearData"
}}}
| Setting | Values / limits |
|---|---|
noiseType |
OpenSimplex2, OpenSimplex2S, Cellular, Perlin, ValueCubic, Value, WhiteNoise, BlueNoise |
whiteNoiseColor |
Monochrome (default), Color (independent RGB); shared by WhiteNoise and BlueNoise |
whiteNoiseSize |
1–1024 canvas pixels per grain, default 1; shared by WhiteNoise and BlueNoise |
dimensions |
TwoD (default), OneD (straight stripes), ThreeD (2D slice at Offset Z; not White/Blue) |
periodic |
UI Seamless: None (default), X (left/right), Y (top/bottom), XY (both pairs); six non-grain noise types in TwoD/ThreeD only |
periodic1D |
Boolean, default false; UI Seamless checkbox in OneD, repeats along the noise axis; ignored outside OneD and for White/Blue |
direction |
−180–180 degrees, default 0; OneD only; 0 varies horizontally (vertical stripes), 90 varies vertically |
seed |
Signed 32-bit integer; passed to the shader as an integer, not a float |
scale |
Scalar sets XY, or XYZ in active ThreeD; [x,y] preserves Z, [x,y,z] sets all axes. Each 0.01–1000; default [8,8,1]. XY spans the shorter canvas side; Z multiplies Offset Z. Inspect returns XYZ including inactive Z |
linkScale |
Boolean, default true; proportional inspector edits, explicit API values are applied literally |
offset |
[x,y] preserves Z, [x,y,z] sets all axes; each −10000–10000 noise-space units (XY canvas pixels for WhiteNoise/BlueNoise). Inspect returns three values |
fractal |
None, FBm, Ridged, PingPong |
octaves, lacunarity, gain |
Integer 1–8; 1–4; 0–1 |
weightedStrength, pingPongStrength |
0–1; 0.01–8 |
cellularDistance |
Euclidean, EuclideanSquared, Manhattan, Hybrid |
cellularReturn |
CellValue, Distance, Distance2, Distance2Add, Distance2Sub, Distance2Mul, Distance2Div |
cellularJitter |
0–1 |
warp, warpStrength |
None, OpenSimplex2, OpenSimplex2Reduced, BasicGrid; 0–100 noise-space units |
warpScale |
number or [x,y], each 0.01–1000, default [1,1]; X/Y multipliers of Noise Scale; Random All samples 0.25–4 and preserves linked proportions |
linkWarpScale |
boolean, default true; UI/Random All chain, explicit API values apply literally |
encoding |
LinearData (default; raw normalized values), ColorValues (display colors), Gradient (monochrome noise mapped through a palette) |
inverted |
Boolean; reverses noise values in every output mode, before palette sampling in Gradient |
gradient |
Shared stops/object, default black-to-white, Perceptual interpolation when mode is omitted; RGBA/HDR palette, retained while disabled |
Without gradient mapping, RGB repeats the normalized scalar, except WhiteNoise/BlueNoise with Color which generates independent RGB; alpha is 1. Gradient output supplies RGB and alpha through the same encoded LUT and linear decode as SDF. Inverted remains available and reverses values before palette sampling. Color WhiteNoise/BlueNoise supports only ColorValues/LinearData: a retained Gradient setting temporarily renders/displays ColorValues, then returns to Gradient on switching to monochrome. Changing Output preserves the palette. Random All preserves gradient and keeps encoding:Gradient; otherwise it randomizes encoding only between ColorValues and LinearData. It can vary inverted; for grain noise with stored Gradient output it also preserves the Color setting so effective Output cannot change. Noise operations put the palette inside settings.noise; unified document/clipboard JSON uses behaviour.gradient on NoiseLayerBehaviour. Noise has no useGradient field.
UI Output maps to encoding, Seamless to periodic (TwoD/ThreeD) or periodic1D (OneD), and the Scale chain to linkScale.
There is no Noise seamless boolean, scaleY, scaleZ or offsetZ API field: use periodic/periodic1D, scale:[x,y,z]
and offset:[x,y,z]. Supplying gradient alone does not enable it: also set encoding:"Gradient".
Gradient updates replace the whole palette, not individual keys. Omitted fields in settings retain
their existing values. Inspection reports stored settings, even when a type/dimension temporarily
ignores them. Unified JSON export uses its selected write mode and frozen version defaults,
not the live editing-operation shapes or current new-layer factory defaults.
For example, this partial update combines a 3D seamless source with an explicit palette:
{"op":"set","layer":"LAYER-ID","settings":{"noise":{
"noiseType":"Perlin","dimensions":"ThreeD","periodic":"XY",
"scale":[6,10,1],"linkScale":false,"offset":[0,0,0.5],"encoding":"Gradient",
"gradient":{"colors":[{"time":0,"color":[0,0,0,1]},{"time":1,"color":[1,1,1,1]}],"mode":"Perceptual"}
}}}
FastNoiseLite output is remapped from signed noise to 0–1 and clamped.
WhiteNoise hashes discrete canvas-space cells with the signed integer seed. It ignores scale, fractal,
cellular and warp settings without resetting them; whiteNoiseSize controls its grain size instead.
It supports inversion, encoding and OneD direction, and keeps its grid independent of preview resolution.
BlueNoise uses WhimTex-generated periodic void-and-cluster rank tables: 128×128 RGB in 2D and
a separate 256-sample RGB sequence in 1D. Seed hashes select translations/reflections (plus axis swaps in 2D),
not an expensive runtime rebake. Each channel has a separately generated rank table. Grain coordinates,
ignored settings, encoding and inversion match WhiteNoise. Legacy whiteNoise* field names are retained
for both grain types. Offset Y in 1D selects a seeded variation of the sequence.
The tables use 8-bit uniform ranks; use LinearData for raw dither thresholds.
For masks/channel packing, prefer LinearData and apply the existing Swizzle/blend settings.
For a Normal Map or SDF source, add the effect above Noise and assign Previous or a specific target as usual.
Domain Warp uses a single warp pass; noise fractal settings affect the subsequent noise evaluation.
Warp Scale multiplies Noise Scale separately on X/Y. For example, Scale [0.5,2] and
Warp Scale [6,0.5] request final warp scales [3,1]. A scalar API value sets both multipliers.
The chain only affects UI editing and Random All; enabling it retains the current ratio.
Random All chooses X logarithmically from 0.25–4 and scales linked Y proportionally within
the legal range, or samples both independently when unlinked. It retains the chain state.
Seamless fits the resulting warp lattice periods and compensates displacement only for
period fitting. Warp Scale adds no separate Z multiplier; Z remains non-periodic.
Missing multipliers default to 1; no migration is performed. A serialized scalar warpScale
is the X multiplier; absent warpScaleY follows X. The earlier unshipped absolute-scale
prototype is superseded: its stored values now act as multipliers.
One-cell XY BasicGrid can still give uniform displacement; increase Warp Scale to obtain
a varying warp field at small Noise Scale. White/Blue Noise ignore these controls.
OneD projects aspect-correct centered coordinates onto the direction axis before offset and warp.
Offset X moves along the slice and Y selects the slice. Thus warp and fractals preserve stripe invariance.
ThreeD exposes Z in Scale and Offset and evaluates the native 3D kernel, including 3D Fractal and Domain Warp.
The sampled slice coordinate is Offset Z × Scale Z. Missing Scale Z defaults to 1, preserving old files;
1D/2D editing retains inactive Z. With Offset Z = 0, changing Scale Z alone does not move the slice.
Z is never periodic and never advances automatically. Cellular 3D slices differ visibly from 2D cells.
White/Blue retain their 1D/2D behavior: a stored ThreeD temporarily uses TwoD; Periodic is ignored.
OneD ignores periodic, retaining it for a return to TwoD/ThreeD; its separate periodic1D
flag enables the same periodic lattice kernels along the scalar axis before Fractal/Warp.
For canvas W/H and short side S, the period extent is
abs(W/S * ScaleX * cos(Direction)) + abs(H/S * ScaleY * sin(Direction)).
Each octave and warp fits that extent independently; the transverse coordinate stays fixed
before warp. Offset X moves along the slice, Y selects it, Z is ignored. Direction 0 joins
left/right and 90 joins top/bottom. Arbitrarily angled stripes repeat along their axis but
need not join rectangular canvas edges. Both seamless settings are retained independently.
Periodic wraps lattice hashes, not output colors. It fits integer periods independently on selected axes
for every octave and the warp. Each period rounds extent * octaveFrequency / latticeUnit
directly, with a minimum of one cell; 2D simplex no longer rounds the base extent before applying
octave frequency. Sub-unit Scale therefore affects later fractal octaves, but does not remove
the minimum-cell limit of Fractal None. This changes some existing 2D periodic simplex patterns
at small and large scales, without changing stored parameters or non-periodic/3D rendering.
Non-integral lacunarity remains periodic. Square/cubic lattices round
to whole cells; 2D simplex uses an orthogonal basis of its triangular lattice, and 3D simplex fits
external periods in multiples of three lattice units without repeating Z. Small scales can change
in visible steps. Source-rectangle periodicity does not guarantee canvas tiling after arbitrary
layer/group transforms or FX. Extreme aspect/scale/octave combinations exceeding 100 million lattice
units, or base coordinates beyond 500 million lattice units, report an error instead of overflowing
integer indices. High-frequency detail can still alias.
Random All preserves Dimensions, Direction, both Seamless settings (periodic, periodic1D), the linked scale ratio and all Offset components (X/Y/Z).
Linked main Scale samples the arithmetic mean M logarithmically from 1–64, then derives the active axes (XY, or XYZ in ThreeD)
from the retained ratio; swapping axes does not change the distribution of M. Intersect this
mean range with the means allowed by the per-axis 0.01–1000 bounds. If the intersection is empty
for an extreme ratio, use its feasible mean range instead (8 may be unreachable without changing
that ratio). Sampling the feasible interval directly avoids accumulating candidates at a clamp.
Unlinked active axes sample independently, logarithmically from 1–64. Inactive Scale Z is retained in 1D/2D.
Candidates are accepted with probability (1 + exp(-0.5 * log2(M / 8)^2)) / 2,
where M = (X + Y) / 2 in 1D/2D and (X + Y + Z) / 3 in ThreeD. This reweights the candidate distribution
by a factor in [1,2], without extra truncation or clamping at 8. For unlinked
axes the baseline distribution of M is not log-uniform: the cap is on the relative weighting,
not on absolute probabilities of arbitrary numeric bins. Warp Scale sampling is unchanged.
Generation runs on GPU at the requested resolution.
Seed and normalized coordinates are stable across preview/export sizes, but different GPUs may produce small
floating-point differences. Saving stores the procedural parameters through existing document serialization;
the usual baked output texture is still generated when required.
Gaussian Blur settings
strength is 0–4 (default 1; UI 0–400%). Zero bypasses the blur; 0–1 mixes the source and blur in
premultiplied linear RGBA. Above 1, RGB stays unchanged and alpha becomes a*s/(1+a*(s-1)), with
a clamped to 0–1. This matches Motion Blur: denser translucent coverage, not a larger radius or RGB gain.
Radius zero remains an identity operation at every strength. Transform and FX still apply after bypass.
Use type:"blur" and partial settings.blur updates, with mode:"Gaussian" (default).
describe exposes blurDefaults, blurModes, blurDirections and blurEdges;
inspect returns settings for all modes, including inactive ones.
The former gaussianBlur/motionBlur API types and serialized layer classes are removed without migration.
Recreate old blur layers with the new type; request envelope version remains 1.
{"op":"add","type":"blur","as":"blur","settings":{
"colorRange":"HDR","blur":{"mode":"Gaussian","radius":32,"edges":"Repeat"}
}}
Assign a stable source ID (or batch alias) with
{"op":"target","layer":"@blur","input":"Specific","target":"@source"}.
Radius is the finite kernel extent (three standard deviations) in original canvas pixels;
0 bypasses filtering. Sources may be hidden. Groups are sampled against transparency without
changing their Pass Through setting. Layer Transform, swizzle, clipping, opacity and blend settings
apply normally to the effect. API rendering/saving uses the full-quality algorithm, never the main
window’s interactive approximation. Export to PSD rasterizes this effect.
See Gaussian Blur for transparency, HDR and cache behavior.
Sharpen settings
Use type:"sharpen" and partial settings.sharpen updates. algorithm is Gaussian
(smooth unsharp mask) or Adaptive (a local edge-coherence mask that favors coherent edges
over weak, directionless detail). strength is 0..4
(UI 0–400%); radius is 0..32 original canvas pixels. threshold, noiseReduction and
haloSuppression are 0..1. channelMode is RGB or Luminance. edges selects
Transparent, Clamp, Repeat or Mirror sampling outside the source. Zero strength or
radius leaves the source unchanged. noiseReduction further suppresses irregular detail in
Adaptive mode and has no effect in Gaussian mode. It does not remove noise from the source.
Sharpen preserves alpha and does not clamp HDR RGB values.
{"op":"add","type":"sharpen","as":"crisp","settings":{
"sharpen":{"algorithm":"Adaptive","strength":1.25,"radius":1.5,
"threshold":0.05,"noiseReduction":0.15,"haloSuppression":0.4,
"channelMode":"RGB","edges":"Clamp"}
}}
Assign a source with {"op":"target","layer":"@crisp","input":"Specific","target":"@source"}.
The effect supports hidden sources, groups, transforms, ranges, swizzle, clipping and FX
like other targeted effects.
Motion Blur settings
Use the same type:"blur" and partial settings.blur updates with mode:"Linear" or "Circular".
All modes share strength, edges and target; changing mode preserves radius, distance, angle, arc, center and direction.
| Field | Values / meaning |
|---|---|
mode |
Gaussian (default), Linear, Circular |
radius |
0–256 canvas pixels; default 8; Gaussian only |
strength |
0–4, default 1 (UI 0–400%); below 1 mixes with the original; above 1 increases translucent trail density without changing RGB brightness or length; fully opaque pixels stay unchanged |
distance |
0–512 original canvas pixels; default 16; used by Linear |
angle |
−180–180 degrees; default 0; 0 points right, positive turns counterclockwise; Linear |
arc |
0–360 degrees of rotation; default 15; Circular |
center |
[x,y], each 0–1; bottom-left origin, default [0.5,0.5]; Circular |
direction |
Centered (default), Forward, Backward; Forward follows Angle or rotates counterclockwise |
edges |
Transparent (default), Clamp, Repeat, Mirror |
{"op":"add","type":"blur","as":"motion","settings":{
"colorRange":"HDR","blur":{"mode":"Circular","arc":25,"center":[0.4,0.6],"direction":"Centered","edges":"Transparent"}
}}
Assign the source with {"op":"target","layer":"@motion","input":"Specific","target":"@source"}.
Previous uses the sibling below. Hidden sources and isolated group color are supported, just as
for Gaussian Blur. Zero Distance (Linear) or Arc (Circular) bypasses filtering. Inactive-mode
settings are retained when switching modes. Transform, swizzle, clipping, opacity and blend
settings use the normal effect-layer paths. API rendering uses full quality; PSD rasterizes the effect.
See Motion Blur for sampling, alpha, quality and memory details.
Make Seamless settings
Use type:"makeSeamless" and partial settings.makeSeamless updates in shared operations,
including live batches. Unified document/clipboard JSON puts the native fields directly inside
behaviour with $type: "MakeSeamlessLayerBehaviour", without a properties or settings wrapper;
see the clipboard contract and complete recipe.
This is a targeted effect layer, not an FX preset or an input wrap mode.
The following operations create a hidden source and an explicitly targeted effect. Place this array
inside the operations of the chosen batch workflow:
[
{"op":"add","type":"noise","as":"source","settings":{"enabled":false,"noise":{"seed":42,"scale":6}}},
{"op":"add","type":"makeSeamless","as":"tile","settings":{"makeSeamless":{
"mode":"OffsetBlend","edgeWidth":0.2,"offsetTransitionStart":-0.25,
"offsetSeamCorrection":true,"offsetPoissonEdges":"AllEdges"
}}},
{"op":"target","layer":"@tile","input":"Specific","target":"@source"}
]
For an existing effect, use its inspected layer ID in set:
{"op":"set","layer":"LAYER-ID","settings":{"makeSeamless":{
"mode":"PatchQuilting","quiltingEdges":"LeftAndRight","quiltingWidth":0.2,
"quiltingAlongSearch":0.15,"quiltingFeather":50,"quiltingQuality":"Normal",
"quiltingChannels":"Independent","processAlpha":false,
"quiltingSeamCorrection":true,"quiltingPoissonEdges":"LeftAndRight"
}}}
This second example treats checked RGB channels as independent packed maps, preserves input alpha,
and joins left/right edges for horizontal repetition. Prefer Linked for ordinary colored images.
whimtex_describe / WhimTexApi.Describe() exposes makeSeamlessDefaults,
makeSeamlessModes, makeSeamlessPoissonEdges, makeSeamlessHorizontal,
makeSeamlessVertical, makeSeamlessQuiltingQuality and makeSeamlessQuiltingChannels.
Inspection returns every field below under settings.makeSeamless, including inactive-mode settings.
Omitted fields retain their values; switching mode neither resets nor transfers another mode’s settings.
Defaults below apply to new layers created by the registry/API, not raw C# field initializers or
already saved layers. Use MakeSeamlessLayerBehaviour.CreateDefault() for equivalent C# defaults.
Enum names are case-sensitive strings; unknown fields, removed mode names and out-of-range values are rejected.
Shared controls and edge selection
| API field | Allowed values / new-layer default | Meaning |
|---|---|---|
mode |
OffsetBlend (default), Mirror, ScreenedPoisson, PatchQuilting |
UI Method; choose the base algorithm explicitly in reusable recipes. |
processRed, processGreen, processBlue, processAlpha |
Booleans, all true |
UI Channels; unchecked channels are restored from the input after seam processing, before ordinary layer FX/compositing. All false skips seam processing. |
All paired-edge fields below accept AllEdges (default), TopAndBottom, LeftAndRight, None.
TopAndBottom joins the top/bottom borders for vertical tiling; LeftAndRight joins left/right
borders for horizontal tiling. None skips only that pass. The four copy-edge booleans and
Mirror directions do not limit an enabled Poisson pass. Clearing main-pass edges is therefore
not a full bypass unless correction is also disabled or its edges are None.
Poisson correction is global: selected axes wrap, unselected axes use natural boundaries, but interior and unselected edge values can still change. A smaller radius localizes influence more, without making a hard protected center. Tiny images relax seam-slope constraints.
Offset Blend
Blends half-period-shifted copies in boundary bands; histogram compensation reduces contrast loss.
| API field | Range / new-layer default | UI / behavior |
|---|---|---|
leftEdge, rightEdge, bottomEdge, topEdge |
Booleans, all true |
Copy Edges, independent destination bands. All false skips copying only. |
edgeWidth |
0.02–0.5 / 0.2 | Blend Width (%): fraction of each corresponding dimension; bands have at least two pixels. |
offsetTransitionStart |
-1–0.95 / -0.25 | Transition Start (%), relative to Blend Width, not the canvas. |
offsetContrastCompensation |
Boolean / true |
Contrast Compensation. |
histogramContrast |
0–1 / 1 | Strength (%); zero skips histogram analysis. |
offsetSeamCorrection |
Boolean / true |
Poisson Correction, after blending. |
offsetPoissonEdges |
Paired-edge enum / AllEdges |
Poisson Edges, independent of Copy Edges. |
offsetAutoRadius |
Boolean / true |
Automatic Radius: max(0.005, edgeWidth / 4). |
offsetCorrectionRadius |
0.005–0.25 / 0.05 | Manual Radius (%), fraction of the smaller dimension; stored but unused while automatic. |
Mirror
Reflects the opposite edge into the selected destination and blends it back into the original.
| API field | Range / new-layer default | UI / behavior |
|---|---|---|
horizontal |
Off, LeftToRight (default), RightToLeft |
Mirror Direction: left source → right destination, or the reverse. |
vertical |
Off, BottomToTop (default), TopToBottom |
Mirror Direction: bottom source → top destination, or the reverse. |
blendWidth |
0.001–0.5 / 0.2 | Blend Width (%), fraction of each corresponding dimension. |
mirrorTransitionStart |
-1–0.95 / -0.25 | Transition Start (%), relative to Blend Width. |
falloff |
0.25–4 / 1 | Falloff; larger values concentrate reflection near the destination edge. |
mirrorContrastCompensation |
Boolean / false |
Contrast Compensation, histogram mixing with reflected donors. |
mirrorContrast |
0–1 / 1 | Strength (%); zero uses ordinary Mirror. |
mirrorSeamCorrection |
Boolean / true |
Poisson Correction, also runs when both directions are Off. |
mirrorPoissonEdges |
Paired-edge enum / AllEdges |
Poisson Edges, independent of Mirror Direction. |
mirrorAutoRadius |
Boolean / true |
Automatic Radius: max(0.005, blendWidth / 4). |
mirrorCorrectionRadius |
0.005–0.25 / 0.05 | Manual Radius (%), fraction of the smaller dimension; retained while automatic. |
For both blend methods, zero Transition Start begins fading at the edge; positive values delay/narrow the fade, negative values start it outside the canvas, leaving some original at the border. This may reintroduce a seam. New layers already enable Poisson Correction, but changing Transition Start does not toggle it. Check the correction’s enabled state and edge pairs explicitly. With correction off, pixels outside modified bands retain the input apart from floating-point/alpha roundoff.
Screened Poisson
| API field | Range / new-layer default | UI / behavior |
|---|---|---|
poissonEdges |
Paired-edge enum / AllEdges |
Poisson Edges, selecting the standalone solve. None skips it. |
screeningRadius |
0.005–0.25 / 0.05 | Radius (%), fraction of the smaller dimension. |
Uses source-value fidelity and discrete seam-slope constraints. Does not use copy bands, Mirror directions, contrast-compensation flags or the other modes’ correction radii.
Patch Quilting
Searches source boundary-strip donors and joins them using low-error dynamic-programming cuts; not whole-image texture synthesis. Optional Along-Seam Search smoothly displaces donor samples along the strip, tapering to zero at its ends without wrapping; it is not a rigid translation.
| API field | Range / new-layer default | UI / behavior |
|---|---|---|
quiltingEdges |
Paired-edge enum / AllEdges |
Patch Edges; None skips quilting, not independent correction. |
quiltingWidth |
0.02–0.45 / 0.2 | Patch Width (%), fraction of each corresponding dimension. |
quiltingAlongSearch |
0–0.25 / 0 | Along-Seam Search (%), relative to usable strip length. Nonzero divides the same candidate budget between straight and displaced strips. |
quiltingQuality |
Draft, Normal (default), High |
Search Quality; analysis limits 96/160/256 per axis and 8/24/48 candidates. |
quiltingChannels |
Linked (default), Independent |
Channel Matching; one donor/cut using checked premultiplied channels, or separate straight-value searches per checked channel for packed maps. |
quiltingSeed |
Signed 32-bit integer / 0 | Seed; repeatable for the same input, resolution and settings. Different seeds can still select the same donor. |
quiltingFeather |
0–100 / 50 | Feather (%), already in percentage units; share of the safe transition width of each cut, not pixels or a fraction of Patch Width. |
quiltingContrastCompensation |
Boolean / false |
Contrast Compensation within Feather transitions. |
quiltingContrast |
0–1 / 1 | Strength (%); no extra compensation at zero strength or zero Feather. |
quiltingSeamCorrection |
Boolean / false |
Independent global Poisson Correction, after quilting. |
quiltingPoissonEdges |
Paired-edge enum / AllEdges |
Poisson Edges, independent of Patch Edges. |
quiltingCorrectionRadius |
0.005–0.25 / 0.05 | Radius (%), fraction of the smaller dimension; no automatic radius. |
Feather blends across each cut, not a blur of the texture. A one-pixel band cannot feather; on small images several percentages may look identical. Compensation in the first axis can affect donor selection in the second. Higher Search Quality costs more and does not guarantee a better visual match.
Units, alpha and verification
- UI percentages are API fractions (
20%→0.2,-25%→-0.25,5%radius →0.05), exceptquiltingFeather, where50%→50. - Disabled channels retain straight linear input values before subsequent layer processing. Disabling A preserves input alpha, not RGB independence from alpha: premultiplied methods still use input alpha. Independent quilting matching uses straight values; an enabled Poisson correction remains premultiplied. Independent matching is not a color-preserving mode for transparent artwork.
- Histogram compensation can alter colors; Poisson can affect contrast and overshoot RGB. Alpha is bounded; HDR RGB remains available until normal layer/output range handling. No method guarantees invisible joins for arbitrary structured images. Inspect both the tile join and the interior.
- Previous input uses the next sibling below; Specific can target a hidden source or isolated group color. Normal transforms, ranges, Swizzle, FX, clipping and composition still apply and can break the final join. These methods remain editable; raster export evaluates the effect.
- Reduced-size Quilting previews can choose different donors/cuts. Ordinary window preview is capped at 512 px; Live Quality does not remove that limit. Use Pencil without painting plus Tiled for a full-resolution UI check, or render/export at the actual intended resolution through the API.
- Poisson-only changes can reuse the pre-correction quilting cache. Source/search settings and resolution changes require a new search; synchronous readback/job completion can still block the editor.
- Quilting evaluates duplicate candidate strips only once, without changing the same-seed result or selected Search Quality; the benefit can be larger for wider strips. With Contrast Compensation, the first calculation after a long idle period or a resolution change can take longer than subsequent updates.
Normal Map settings
Use type:"normalMap" and put generator settings inside settings.normalMap. Both add and
set accept partial updates. describe exposes normalMapDefaults; inspect returns every
generator setting under settings.normalMap. Regular layer settings, targets, groups, swizzle,
clipping masks, duplication, conversion and raster export use the existing paths.
{"op":"add","type":"normalMap","as":"normal","settings":{"normalMap":{
"mode":"Texture","strength":6,"smoothing":1,"mediumRadius":4,"largeRadius":32,
"fineDetail":1.5,"mediumDetail":1,"largeDetail":0.5,"lightRemoval":0.75,
"edges":"Repeat","encoding":"PackedColor"
}}}
{"op":"target","layer":"@normal","input":"Specific","target":"@art"}
| Key | Values / limits |
|---|---|
mode |
HeightMap (default), Texture |
sourceChannel |
Luminance (default), Red, Green, Blue, Alpha, Maximum |
inputSpace |
ColorValues (default; encoded/display RGB), Linear (working values) |
strength |
0..128, default 4 |
blackLevel, whiteLevel |
0..1 / 0.0001..16; defaults 0/1; white must exceed black |
gamma |
0.05..8, default 1 |
smoothing |
0..64 full-resolution pixels, default 1 |
mediumRadius, largeRadius |
0.5..128 / 0.5..512 pixels; defaults 4/32; large must be at least medium |
fineDetail, mediumDetail, largeDetail |
0..8; defaults 1/1/0.5; Texture mode only |
lightRemoval |
0..1, default 0.75; Texture mode only |
edges |
Clamp (default), Repeat, Mirror; independent of Transform tiling |
derivative |
Sobel (default), Scharr, CentralDifference |
inverted, flipX, flipY |
Boolean, default false |
ignoreTransparent |
Boolean, default true; alpha-normalized smoothing, except when Alpha is height |
alphaMode |
Opaque (default), Source |
output |
Normal (default), Height (reconstructed height for tuning) |
encoding |
PackedColor (default; PNG/TGA/PSD/display), LinearData (raw linear EXR/Texture2D data) |
Normals are tangent-space vectors packed into 0..1 RGB. Alpha is coverage, not a packed X channel. Positive height gradients tilt the normal toward negative X/Y; flips reverse each respective axis. Texture mode uses differences between smoothed height bands, not geometry or material recognition. Its Light Removal attenuates the broad band and may remove real relief too.
A group source is rendered against transparency with its own descendants, opacity, swizzle and
clipping, without the external backdrop. Outline and Alpha-source SDF use group coverage;
SDF with Red/Green/Blue/Luminance uses the group’s color result.
Like other effect layers, Normal Map processes hidden sources: enabled:false hides a layer’s
own contribution, not its availability to Previous/Specific consumers. Hidden groups still
respect their children’s visibility. This also applies to chains of hidden effect layers.
Opacity and clipping semantics are unchanged; missing and cyclic targets remain invalid.
Keep the resulting normal layer at full opacity with Normal blend, identity swizzle and no color FX when exporting a normal texture. Color blending does not renormalize normals. Transform moves the output image without rotating its vectors. PackedColor compensates for the compositor’s LDR gamma encoding; LinearData is the appropriate choice for raw linear output, not ordinary PNG export. Import exported packed PNG/TGA as Normal Map with grayscale conversion disabled; see the Unity normal-map import reference. The API does not change source or exported texture import settings automatically.
Transforms and coordinates
Transform patches support position:[x,y], scale:[x,y], pivot:[u,v], rotation, tiling,
reset:true, originalAspect:true.
- Position is in canvas pixels: positive X goes right, positive Y goes up.
- Rotation is counterclockwise degrees.
- Pivot is bottom-left UV:
[0.5,0.5]is the center. - Scale
[1,1]means the full canvas-sized source rectangle; negative values mirror, zero is rejected. - Tiling is
Source,Clip,Repeat,Mirror,ClamporUnbounded.Sourcereads the source texture’s U/V wrap modes.Clampextends edge pixels.Unboundedcontinues Noise, Gradient, Color Fill and Shape calculations outside 0–1 UV; raster layers use Clip. resetis applied before the other fields, Original Aspect after them.- Changing pivot through this API uses raw TRS semantics; it does not compensate position. In matrix mode it changes only the pivot.
matrix:[m00,m01,m02,m10,m11,m12,m20,m21,m22]sets a double-precision projective transform from source UV to canvas UV; divide the first two output coordinates by the third. It must be invertible with no horizon crossing the unit source rectangle.- Do not combine
matrixwithposition,scale,rotationororiginalAspectin the same patch. Pivot and tiling are independent. - Scalar edits on an existing matrix preserve its distortion. Rotation/Scale describe the local axes at the pivot; Reset returns TRS. Original Aspect requires TRS.
- Brush footprints are defined in canvas pixels and inverse-compensated when written to a transformed Drawing source.
Drawing strokes
{
"op":"stroke", "layer":"@ink", "space":"canvasPixels", "erase":false,
"brush":{"color":[1,0.2,0.1,1], "size":12, "hardness":0.8, "spacing":0.16},
"points":[[100,100],[180,140],[240,110]]
}
Each stroke has 1..4096 points: one point is a dab, multiple points form a polyline. Use sparse points
for straight segments; the brush interpolates stamps. Curves can be sampled as a polyline.
The brush uses the same renderer and source-over alpha as manual painting. erase:true uses the eraser.
No layer selection, canvas-area selection or RGBA Preview mask is inherited from the window:
specify the desired RGBA explicitly. Rectangle/lasso coverage and the internal pixel clipboard
are temporary window tools, not serialized document data or API stroke parameters.
For pixel-aligned pencil strokes, add "pencil":"Circle", "Square", or "Diamond" to the
stroke operation (not inside brush). Omit it for the regular soft brush. Pencil uses brush.size
rounded to whole pixels (1–4096), a hard edge and contiguous pixel steps; hardness and spacing do not
affect pencil strokes. Colors, HDR, erase, layer transforms, symmetry and repeat clipping still apply.
The pencil choice is per operation and does not change the window’s selected tool.
{"op":"stroke","layer":"@ink","pencil":"Square","brush":{"size":3,"color":[1,0,0,1]},"points":[[8,8],[24,16]]}
Color alpha zero leaves no mark, including for the eraser; eraser strength otherwise follows alpha.
space:"canvasPixels"(default): top-left origin, X right, Y down. The API inverts the layer’s transform to place ink under that canvas position. This mode requires Clip tiling.space:"layerUv": bottom-left origin in the untransformed source tile;[0,0]bottom-left,[1,1]top-right. Useful for repeating transforms, where multiple visible copies share one source.- Brush size is a diameter in canvas pixels. The footprint is inverse-compensated when written into a transformed Drawing source; a nonuniform layer scale does not stretch the visible brush.
brushis optional; supplied fields update the layer’s saved brush settings. Missing fields retain their current values, including symmetry/repeat. Setrepeat:"None"explicitly when a one-off unmirrored stroke is intended.- API brush parameters remain independent of the window’s shared interactive brush/color/fill preferences. API strokes do not read or change those preferences. Symmetry/repeat and transforms are layer-local and are shared by API and interactive painting.
| Brush field | Values |
|---|---|
color |
RGBA array: encoded RGB -107..107, alpha 0..1; Standard clamps the painting color |
size |
1..4096 |
hardness |
0..1 |
spacing |
0.01..4, fraction of brush size (0.16 = 16%) |
opacity |
0..1, default 1; caps the complete stroke, not individual stamps |
flow |
0..1, default 1; multiplies each stamp’s alpha before accumulation |
pressure |
Boolean, default true; interactive painting multiplies brush opacity by tablet pressure. API strokes use full pressure |
scatter |
0..4, default 0; random disk radius in brush diameters |
scatterBias |
−1..1, default 0 (UI −100..100). Negative concentrates centers near the stroke; positive near the scatter disk edge. With uniform sample u, normalized radius is u^(0.5 * 2^(-4 * scatterBias)); zero preserves sqrt(u), uniform by area. Applies to Random and Sobol without consuming extra random values; ignored when scatter is zero |
sizeJitter |
0..1, default 0; size multiplier sampled from 1−jitter to 1+jitter, minimum one pixel |
rotationMode |
Fixed (default) or StrokeDirection. For texture tips, adds the source-pixel-space segment angle before jitter; +X is zero, +Y is +90°. Stationary points retain the last direction; a new stroke starts at zero. Scatter does not affect direction. Ignored without a tip texture |
angleOffset |
−180..180 counterclockwise degrees, default 0; constant offset added to rotationMode before angleJitter. Ignored without a tip texture |
angleJitter |
0..180 degrees, default 0; offset sampled from −jitter to +jitter per stamp, added after rotationMode and angleOffset. Ignored without a tip texture |
flipX, flipY |
0..1, default 0; per-stamp horizontal/vertical reflection probabilities in tip-local axes, before rotation. 0 never flips, 1 always flips; intermediate probabilities sample each axis separately. Ignored without a texture tip. Symmetry copies share the chosen flips |
randomAlgorithm |
Random (default) or Sobol; controls scatter, size, angle, tint and flip sampling. Sobol uses seven fixed dimensions (scatter angle/radius, size, angle, tint, flip X/Y) and a seeded digital shift; enabling tint or flips does not perturb scatter. The sample index continues across stroke segments, including clipped stamps, and resets per stroke; symmetry copies share a sample |
tintGradient |
1..64 ordered {time, color} stops, like a Gradient layer; default opaque white. Differing RGB or alpha keys trigger random sampling per stamp; identical keys give a constant multiplier without consuming random samples. Reset by supplying two opaque-white stops |
tip |
Imported Texture2D asset path, or null for a procedural brush. The document’s own output is rejected. Does not change texture import settings |
tipChannel |
Alpha (default), Luminance, InvertedLuminance, Color. Ordinary tips use alpha for coverage, optionally multiplied by luminance/inverted luminance. Color also multiplies painting RGB by the tip RGB |
proceduralMode |
Hardness (default) or SdfGradient. Used only when tip is null; independent of textured tipSdf. Procedural gradient coordinate is radius, where radius is distance from stamp center divided by half the brush Size: center 0, edge 1. Pixels outside the circular tip are discarded. Uses the shared tipGradient, including its RGB and alpha; Pencil ignores this mode |
tipSdf |
Boolean, default false. For a textured brush, sample tipGradient at 1 − selected field. Alpha and Color use alpha as the field, without multiplying the original alpha again; luminance modes use brightness then multiply coverage by original alpha. Higher field values are inside, corresponding to lower gradient coordinates. Ignored without a texture tip |
tipGradient |
1..64 ordered {time, color} stops. Default white with alpha 1 at 0.4 and alpha 0 at 0.6. Shared by procedural SdfGradient and textured SDF modes. Coordinates run from interior 0 to outer edge 1: RGB multiplies brush/tint RGB, alpha supplies coverage. Replaces tipThreshold; hardness no longer affects SDF. Inspect returns tipGradientKeys (separate colors/alphas). A cached 1024×2 linear RGBAHalf premultiplied LUT, clamped and mip-filtered, approximates the gradient on GPU. Editor Standard input mode removes key intensity without changing stored values; API strokes always use supplied values |
blend |
Layer BlendMode names except Overwrite and None; default Normal. Application is controlled by blendApplication; ignored for erase |
blendApplication |
Stroke (default) or Stamp. Stroke blends accumulated source-over stamps against the pre-stroke layer. Stamp blends each stamp against the evolving layer, so earlier stamps participate. Flow affects each stamp; Opacity interpolates the pre-stroke and fully accumulated results once in premultiplied linear space, without feeding that interpolation into subsequent stamps. Normal and erase retain the existing equivalent fast paths. Symmetry stamps are applied in their generated order; periodic copies of one stamp share its backdrop |
seed |
Integer 1..2147483647, default 1; repeatable random sequence, restarted per stroke |
mirrorX, mirrorY |
Vertical/horizontal axis reflection respectively; used only with repeat:"Mirror" |
center |
Bottom-left UV, each component 0..1 |
repeat |
None, Mirror, Horizontal, Vertical, Grid, Radial (mutually exclusive) |
repeatCount, repeatSecondaryCount |
Integers 2..64; secondary is the grid Y count |
radialStartAngle |
0..360 degrees counterclockwise from the left; Radial only, default 0 preserves the original layout |
mirrorAngle |
0..360 degrees counterclockwise around center; Mirror only. Rotates both axes in source-pixel space. Default 0 keeps the original vertical/horizontal axes |
elements |
Copy, AlternateMirror |
boundary |
Continue, Clip |
For simple symmetry, set repeat:"Mirror" and at least one of mirrorX/mirrorY to true.
Stamp spacing carries across pointer segments; adding more points on the same straight path
does not add more stamps. Random variations are shared by symmetry copies of each stamp.
Opacity applies once per API stroke; separate strokes can build past that limit.
Texture tips preserve their aspect ratio; size is the longest side. Hardness controls procedural brushes in Hardness mode only;
SDF uses its gradient. SDF fields use the normalized 0–1 range. Luminance is measured
in source-encoded RGB for sRGB textures, preserving the threshold between Gamma and Linear projects.
Pencil ignores all advanced dynamics and texture-tip settings.
Both tipGradient and tintGradient accept a stop array or the shared gradient object with colors,
optional alphas, mode, wrapMode, smoothness and colorSpace. Supplying either field replaces
that entire gradient; omitting it preserves the existing gradient. For supplied gradients without
mode, both tipGradient and tintGradient default to Perceptual, matching brush
clipboard import. Explicit modes are preserved. This applies to settings.brush and stroke brush.
Inspect returns tipGradientKeys / tintGradientKeys, including separate color/alpha tracks and
interpolation settings; write their gradient bodies using tipGradient / tintGradient respectively.
For example, "brush":{"size":40,"spacing":0.8,"opacity":0.6,"flow":0.2,"scatter":0.5,"sizeJitter":0.3,"seed":123}
creates repeatable scattered stamps with a 60% stroke-opacity cap.
Mirror axis choices are retained but ignored in other modes. center affects Mirror and Radial;
elements applies only to Horizontal, Vertical, Grid and Radial. boundary also applies to Mirror.
For reflected radial sectors, use repeat:"Radial", elements:"AlternateMirror".
Legacy mirror-only settings migrate to Mirror; legacy Repeat+Mirror uses Repeat without extra mirrors.
Clip anchors to the stroke’s first cell/sector and terminates the polyline at its first exit. In Mirror mode it anchors to the starting side of each enabled, rotated axis; each reflected brush footprint is clipped to its own half-plane or quadrant. Continue allows crossing axes and overlapping brush footprints. Mirror without enabled axes is unrestricted. Start a new stroke to draw in another segment. Continue permits crossing segment boundaries. The validator caps estimated replicated stamps at 100,000 per stroke, covered brush pixels at 250,000,000 per stroke and source UV at -4..5. Documents support up to 1024 layers and 67,108,864 total owned Drawing pixels through the API.
Color Fill patterns
Use settings.fillMode:"Pattern" and partial settings.fillPattern updates.
describe returns fillModes and fillPatternDefaults; inspect includes the full
pattern. Unified document/clipboard JSON uses behaviour.mode and behaviour.pattern
on ColorFillLayerBehaviour; use the document schema for their native field shapes.
| Field | Values |
|---|---|
shape |
Triangles, Squares, Hexagons (default), Circles |
circleLayout |
Square, Dense (default) |
size |
[x,y], each 1..16384 px, default [64,64]; grid scale on each axis before transforms. A scalar sets both axes |
linkSize |
Default true; inspector edits scale both axes proportionally. Explicit API sizes are applied as given |
rotation, offset |
±360000 degrees; [x,y] in pixels, each ±1000000 |
seamless |
false by default; fit complete rectangular periods to canvas |
gap, roundness, bulge |
0..0.99, 0..1, 0..1; all default zero |
distanceRange |
.001..16 in figure inradii, default 1 |
position |
Outside, Inside, Center, Signed (default) |
inverted |
Boolean, default false |
profile |
FX curve string syntax, default linear |
gradient |
Standard gradient stops/object; default white to black |
cellColor |
Uniform (default), Random, Pattern |
colorBlend |
Multiply (default), ReplaceRGB; both preserve SDF alpha |
palette |
Standard gradient stops/object, default white to black; RGB only. Fixed mode provides discrete colors |
seed |
Signed 32-bit integer, default 0; Random only |
variation |
0..1, default 1; Random palette sampling from midpoint (0) to full range (1) |
Signed distance is negative inside and positive in gaps; zero maps to the middle of the gradient. Bulge remaps only the interior, so nonzero Bulge is not an exact distance. Seamless snaps combined layer/group rotation to quarter turns and fits each axis. Shear/perspective become an axis-aligned approximation at canvas center; layer tiling is bypassed. Stored settings stay unchanged. Polygons may stretch; circles remain circular with extra space on one axis. Later FX can break periodicity.
Cell colors use the nearest contour, including gaps, and follow the procedural coordinates. Pattern uses palette positions 0/1 for checkerboards and triangle orientations, 0/.5/1 for hexagons and dense circles. Seamless also wraps Random cell IDs and fits Pattern color periods: even X/Y counts for square grids, X multiples of three for hexagons/dense circles; staggered grids retain complete row pairs. Changing palette colors does not change the geometry.
Validation, Undo and recovery
- Every batch is preflighted on a detached model.
dryRundoes not prove GPU availability, successful image decoding, successful new HLSL compilation or writable disk space. - TIFF Batch: opens an independent model, applies operations, optionally saves, then disposes
the model.
save:falsereturns a snapshot but discards the edits. It cannot edit or save the user’s open document. Saving uses an atomic TIFF replacement, but import and other I/O are not one filesystem transaction. An error can occur after the file has committed. - Headless Live: retains a candidate between requests until completion/cancellation or reload; see its lifecycle. It does not provide user Undo.
- Assistant: edits the open document with Undo, never autosaves. After Undo/Redo, save through the window if persistence is wanted. An empty TIFF Batch does not save unsaved Assistant edits.
- Internal rollback of a failed Batch is not a user Undo contract for the written TIFF.
- A revision hashes serialized model state, materialized Drawing pixels and FX state; it is not a complete guarantee against changes to every external texture or include file. It is an opaque optimistic-concurrency token, not a portable version-control ID. Re-inspect after Undo, save, import or domain reload. Do not cache it across sessions.
- On
revision_conflict, inspect and reconsider the patch. Onalready_exists, inspect/reuse the asset or choose a new path; do not delete it to make the request succeed. - On a lost response/timeout, the command may already have executed. Inspect layer IDs/names and render before deciding what remains. Do not automatically retry additions/strokes.
- On
saveMayBePartial:true, Batch reached the save stage, but its temporary model will be discarded. Inspect the on-disk document,statusand staged recovery files first. The old TIFF may still be present, or the new TIFF may already have committed. An empty batch cannot recover discarded edits. Reconstruct only changes confirmed missing, using a fresh revision; never blindly replay additions or strokes. Recover a valid staged TIFF into a new destination when appropriate. - If an import fails after copying, the response reports
fileCreated:trueand the retained path. - If reverting a failed edit also fails,
rollbackFailed:truereports that explicitly. Stop and inspect; neither the old state nor a fully applied batch can be assumed.
Destinations stay under Assets (not StreamingAssets); path traversal and write-through symlinks are
rejected. Preview PNGs are limited to Temp/WhimTex/ and do not overwrite unless requested.
Import accepts at most 64 MiB and 16,777,216 source pixels. Resource limits protect the editor from
accidental huge requests, but effect-heavy documents can still take time: use preview resolution
appropriately and keep batches focused. The API executes on the main thread; it is not an async job queue.
Verification
Select agent-api-v2 through the independent test runner, with a reviewed fingerprint and explicit intended project.
It uses only GUID-owned fixtures under Assets/WhimTexTestMigration and cleans up its own fixtures and previews in
finally; it does not edit existing documents. It verifies TIFF create/inspect, aspect/transform,
preflight rejection, revision conflicts, painting, transient save:false isolation and save/reopen.
agent-editing-v2 covers the shared editing operations and Assistant Undo separately.
Do not run it when the project’s rules prohibit creating test assets.
drawing-pattern-smoke-v2 is a separate opt-in structured regression
test for mutually exclusive Mirror/Repeat modes, file-baseline normalization, movable mirror centers,
source stamps under the cursor and JSON round-trips. It creates no assets or GPU resources; run only
after the updated plugin is compiled. Acknowledge each selected scenario’s declared effects;
this does not grant asset or user-state authority. It does not replace visual painting checks.