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

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:

  • presetPath for a .hlsl file inside Assets, Packages or the configured Shader FX user-preset folder. Presets must start with a valid // @whimtex-effect Category/Name header.
  • source for a raw HLSL string implementing WhimTex’s float4 ApplyFX(float2 uv, float4 color) function. It may include // @param declarations and does not need the preset header. Raw source with relative #include paths must also supply includeBasePath, pointing to a base directory in Assets or Packages, or to an existing HLSL file whose directory is used as the base. Absolute directories inside Assets, a package, or the configured user-preset folder are also accepted. If the source has no relative includes, omit includeBasePath. Unity, Assets/... and Packages/... 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. list accepts an optional TIFF assetPath filter; other calls address an explicit sessionId.
  • begin requires a unique 1..128-character sessionId. 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 from inspect, not the disk SHA-256 from status. For creation use create:true, optional width/height (default 512, each 1..16384, at most 16,777,216 pixels), and omit expectedRevision. The destination must not exist.
  • begin is not idempotent: after a timeout, use list/status, not another blind begin. Preview replay remembers only the last nonempty operation request with a requestId. Repeating that ID requires identical arguments; changing them returns request_conflict. Replayed additions may receive new IDs. Use @aliases inside 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/cancel receipts are persisted for identical terminal retries; these receipts do not restore an active session. Reusing an ID in a new begin invalidates its old receipt. status addresses active sessions, not terminal receipts.
  • complete without new operations saves the current candidate and closes the session; cancel discards 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:true retains 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

  1. Generate a PNG/JPEG using the agent’s image tool, or use a user-provided local image.
  2. 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.

  1. Write a JSON batch outside Assets, for example Temp/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"}
    }
  ]
}
  1. 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
  1. 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: types file, drawing, group, color, gradient, noise, shape, outline, sdf, normalMap, blur, sharpen, makeSeamless, shaderProcessor. Optional parent defaults to root, index to 0. settings and transform are optional patches.
  • set: requires layer and settings.
  • transform: requires layer and transform.
  • move: index is the insertion index after removal from the old container; omitted parent or parent:"" 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 field effectInputModes lists 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), except quiltingFeather, where 50% → 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, Clamp or Unbounded. Source reads the source texture’s U/V wrap modes. Clamp extends edge pixels. Unbounded continues Noise, Gradient, Color Fill and Shape calculations outside 0–1 UV; raster layers use Clip.
  • reset is 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 matrix with position, scale, rotation or originalAspect in 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.
  • brush is optional; supplied fields update the layer’s saved brush settings. Missing fields retain their current values, including symmetry/repeat. Set repeat:"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. dryRun does 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:false returns 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. On already_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, status and 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:true and the retained path.
  • If reverting a failed edit also fails, rollbackFailed:true reports 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.