Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ollm/opencomic-ai-training/llms.txt

Use this file to discover all available pages before exploring further.

The drawing module is the core orchestrator of the dataset generation pipeline. For each image it calls options.setCurrentImageRand() and options.randomize() to produce a deterministic randomised config, then communicates with a running Krita instance via the krita module to create a new document, add the required group and paint layers, execute every configured drawing type (lineart, colorize-mask, background, texture, paint, dots, lines, halftone), export the clean render, and finally hands off to the output and sharp degradation modules to produce and save each lossy variant. The whole cycle is repeated for every entry in degradations[] and for every degraded-image-per-clean-image count requested by the config.

drawing.generateImage()

Generates one complete clean-plus-degraded image pair (or set of pairs) for the given 1-based image index.
async function generateImage(
  image: number,
  setProgress: (image: number, degradedImage: number) => void
): Promise<void>
image
number
required
The 1-based index of the clean image to generate. Passed directly to options.setCurrentImageRand() to seed the per-image RNG and used to build output file names.
setProgress
(image: number, degradedImage: number) => void
required
Callback invoked after each degraded variant is successfully saved to disk. Receives the current clean image index and the running total count of degraded images written so far (across all images in the run). Used by the CLI progress bar in index.mts.

Execution steps

The function executes the following sequence every time it is called: 1. Initialise per-image state
krita.cleanLayerCache();
_options.setCurrentImageRand(image);
_options.resetValues();
brush.reset();
const options: Options = cloneDeep(_options.get()!);
The Krita layer-pixel cache is cleared, the per-image RNG is seeded, any stale if-condition values from the previous image are wiped, and the brush state is reset. A deep clone of the loaded Options is taken so that randomisation does not mutate the master config. 2. Randomise the config for this image
imageOptions = {
  ...options,
  drawings: _options.randomize({ _: cloneDeep(options.drawings) }, 1)._,
  base:     _options.randomize(options.base),
  postProcessing: _options.randomize(options.postProcessing),
  currentImage: image,
  groupLayer: 'general',
};
options.randomize() is called separately on drawings (depth 1, so only the list entries are resolved, not their full sub-trees), base, and postProcessing. Dimensions are snapped to a multiple grid if the config specifies one, then scaled by base.scale. 3. Create the Krita document
await krita.document(width, height);
await krita.fillBackgroundLayer({ r, g, b, a: 255 });
The existing document is resized and all old layers are removed. The background layer is filled with the randomised background color (RGB or grayscale from base.background). 4. Add group and paint layers Depending on imageOptions.drawings.type:
  • 'singlelayered' — one group layer 'general' and one set of layers for area 'all'.
  • '3layered' — one group layer 'general' and three sets of layers for areas 'up', 'middle', and 'down'.
Each area gets:
  • A paintlayer named opencomic:lineart:<area> inside the group.
  • A colorize mask renamed to opencomic:colorize-mask:<area>.
  • Optionally a opencomic:lineart-texture:<area> layer if 'lineart-texture' is in the drawing list.
  • Optionally a opencomic:lineart-random:<area> layer if 'lineart-random' is in the drawing list.
All layer creation is done via krita.send(). 5. Draw each layer For every area, processLayer() iterates imageOptions.drawings.list and dispatches each drawing type:
Drawing typeModule called
'lineart'lineart.draw(imageOptions, drawing, area)
'lineart-texture'lineart.draw(imageOptions, drawing, area, 'lineart-texture')
'lineart-random'lineart.draw(imageOptions, drawing, area, 'lineart-random')
'colorize-mask'colorizeMask.colorize(imageOptions, drawing, area)
'paint'paint.draw(imageOptions, drawing, area, draws)
'dots'dots.draw(imageOptions, drawing, area, groupLayer, draws)
'circles'dots.circles(imageOptions, drawing, area, groupLayer, draws)
'circles-with-dot'dots.circlesWithDot(imageOptions, drawing, area, groupLayer, draws)
'parallel-lines'lines.parallel(imageOptions, drawing, area, groupLayer, draws)
'grid'lines.grid(imageOptions, drawing, area, groupLayer, draws)
'texture'texture.add(imageOptions, drawing)
'gradient'gradient.add(imageOptions, drawing)
The types 'circles', 'circles-with-dot', 'parallel-lines', 'grid', and 'gradient' are recognised by the runtime dispatch switch in drawing.mts but are not part of the TypeScript Drawing union type in types.mts. They can be used in YAML config files and will be dispatched correctly at runtime.
If a drawing’s prob evaluates to false for the current image it is skipped entirely. If a drawing has a skipIf list and any of its entries appear in the list of already-completed drawing types, it is also skipped. 6. Process degradations processDegradations() is called with the completed layers map. For every degradation in imageOptions.degradations and every repeat in 1..degradedImagesPerCleanImage: a. inKrita operations — any halftone filter layers are configured via halftone.config() and applied to the Krita document. krita.refreshProjection() forces a full re-render. b. Clean exportoutput.clean() exports the current document state as a PNG buffer (the “clean” half of the pair). c. Degraded export — halftone layers are re-applied in their visible state and output.degraded() exports the degraded document state. d. inNode operations — the clean and degraded buffers are each passed through the enabled sharp operations in declaration order:
inNode.typeSharp operation
'resize'sharp.resize()
'resize-blur'sharp.resizeBlur()
'blur'sharp.blur()
'rotate'sharp.rotate() on both clean and degraded
'jpeg'sharp.jpeg() on degraded only
'webp'sharp.webp() on degraded only
'avif'sharp.avif() on degraded only
'jxl'sharp.jxl() on degraded only
Operations with both: true are applied to the clean buffer as well as the degraded one. e. Save — if checkSizes passes (or is disabled), the clean buffer, degraded buffer, and a JSON snapshot of all options/configs are written to the three output directories configured in degradation.output. 7. Close / retry logic generateImage() itself does not close the Krita document between images — the document is reused and resized at the start of each call. Error handling lives in index.mts: if generateImage() throws, the caller decrements image and retries, ensuring no index is permanently skipped due to a transient Krita error.

krita module

The krita module (src/krita.mts) is the low-level bridge between Node.js and a running Krita instance. It spawns Krita as a child process, connects via a WebSocket to the kra-remote plugin running inside Krita, and exposes promise-based wrappers for every plugin command.

krita.init()

Launches Krita, waits for the process to signal readiness, connects the WebSocket, and sends an initial edit_layer command to verify the connection.
async function init(execKrita: string): Promise<void>
execKrita
string
required
Absolute path to the Krita executable. When an empty string is passed, Krita is assumed to already be running and only the WebSocket connection step is performed.

krita.send()

Sends a command string to the kra-remote WebSocket plugin and returns a promise that resolves with the plugin’s response payload.
async function send(message: string): Promise<any>
message
string
required
A colon-delimited command string. The part before the first colon is the command key; everything after is the JSON payload. For example: 'add_layer:{"name":"opencomic:lineart:all","type":"paintlayer"}'.
The promise resolves when the plugin sends back a message whose key matches the command key. Structured response types (layer_image, filter_properties, layers, resources, etc.) are parsed automatically; all others return the raw string payload.

krita.getAllResources()

Loads all available brush presets, gradients, patterns, and palettes from Krita and stores them in the module-level resourcesData object.
async function getAllResources(): Promise<void>
Must be called once after krita.init() before any drawing code that references krita.presets or krita.gradients. The brush presets are parsed through parseResources(), which assigns each preset its PresetCategory and letter prefix.

krita.filterProperties()

Retrieves the current property values for a named Krita filter.
async function filterProperties(opts: object): Promise<any>
opts
object
required
An object with at least a filter key naming the filter to inspect (e.g. { filter: 'halftone' }). Returns an object with filter and properties fields.
Used by the halftone drawing module to read the default screentone filter parameters before overriding them for each layer.

krita.checkRestartKrita()

Increments an internal counter and, when it exceeds restartEvery, kills the current Krita process and calls krita.init() again with a fresh instance.
async function checkRestartKrita(execKrita: string, restartEvery: number): Promise<void>
execKrita
string
required
The Krita executable path passed back to krita.init() on restart.
restartEvery
number
required
Restart Krita after this many calls. Pass 0 to disable automatic restarts.
Krita accumulates memory over time during long generation runs. Setting restartEvery to a value between 20 and 100 (depending on document complexity) keeps memory usage stable at the cost of a brief pause per restart.

krita.setReject()

Sets the promise rejection handler that is called when the Krita child process closes unexpectedly during an active image generation.
function setReject(reject: (error: any) => void): void
reject
(error: any) => void
required
The rejection callback for the currently active image’s promise. Called with the process exit code if Krita closes while a generation is in flight.

krita.presets

A Record<string, any> keyed by preset name containing each brush preset’s metadata (name, filename, category, and letter prefix). Populated after getAllResources().
get presets(): Record<string, any>

krita.gradients

The raw gradients object returned by Krita’s resource API. Populated after getAllResources().
get gradients(): any

BRUSHES_MAX_SIZE

A Record<string, number> mapping brush preset names to their recommended maximum brush size in pixels. The krita.editView() function clamps brushSize to this value before sending it to Krita, preventing brushes from being used at sizes that produce undesirable or broken results.
export const BRUSHES_MAX_SIZE: Record<string, number> = {
  'c) Pencil-6 Quick Shade':          500,
  'f) Bristles-1 Details':             40,
  'f) Bristles-2 Flat Rough':         100,
  'f) Bristles-3 Large Smooth':       300,
  'f) Bristles-4 Glaze':              300,
  'f) Bristles-5 Flat':                40,
  'f) Charcoal Rock Soft':            100,
  'g) Dry Bristles':                  300,
  'g) Dry Bristles Eroded':           100,
  'h) Chalk Grainy':                  150,
  'j) WaterC Special Blobs':          500,
  'j) WaterC Special Splats':         500,
  'j) Watercolor Texture':            200,
  'j) Waterpaint Hard Edges':         300,
  't) Shapes Mecha':                  200,
  't) Shapes Rounded':                200,
  't) Shapes Spikes':                 200,
  'v) Texture Impressionism':         500,
  'y) Texture Big':                   400,
  'y) Texture Crackles':              100,
  'y) Texture Hair':                  100,
  'y) Texture Large Splat':           200,
  'y) Texture Noise':                 100,
  'y) Texture Random Particles':      100,
  'z) Stamp Bokeh':                   100,
  'z) Stamp Floor':                   200,
  'z) Stamp Grass':                   150,
  'z) Stamp Grass Patch':             100,
  'z) Stamp Herbals':                 100,
  'z) Stamp Mountains Distant':       300,
  'z) Stamp Sparkles':                100,
  'z) Stamp Stylised Tree':           100,
  'z) Stamp Vegetal':                  40,
  // 33 entries total
};

BRUSHES_MIN_SIZE

A Record<string, number> mapping brush preset names to their recommended minimum brush size in pixels. Brushes with hard pixel edges at small sizes are listed here; editView() clamps upward to this minimum.
export const BRUSHES_MIN_SIZE: Record<string, number> = {
  'c) Penclil-1 Hard':          3,
  'c) Penclil-2':               5,
  'c) Pencil-3 Large 4B':       5,
  'd) Ink-1 Precision':         5,
  'd) Ink-7 Brush Rough':       5,
  'h) Charcoal Pencil Large':   5,
  'h) Charcoal pencil large':   5,
  'h) Charcoal Pencil Medium':  5,
  'h) Charcoal Pencil Thin':    5,
  // 9 entries total
};
Brushes listed in BRUSHES_MIN_SIZE are no longer excluded from random selection; they are only size-clamped upward when brushSize would otherwise fall below the listed minimum.

Build docs developers (and LLMs) love