Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Appendix C: the probes

Each probe answers one question of the form “does this actually work in headless Krita”, and its conclusion goes into the main text. The main text does not name them – it states the conclusion and points here; the last column is the way back, naming the chapter each probe’s conclusion is used in. They all live in probes/; how to run them is in appendix A.

probequestionconclusionchapter
probe_stroke_budget.pyHow many strokes a second at 8K? Does changing colour or brush size need a round of the event loop?paintLine runs at 450 to 850 a second regardless of size; changing colour without the event loop works (24 out of 24), and with it drops to 71 a second12, 18
probe_throughput_tree.pyWhy is it slow on a real layer tree, and why does it get slower as it goes?the tree costs only 7% more; path() is 2.3 times faster than paintLine segment by segment; setBatchmode does nothing. “233 KB of undo stack per call” was this probe’s own measuring mistake, overturned by probe_checkpoint.py12, 18
probe_taper.pyWhich of 36 presets taper, and which have clean edges?the mypaint engine reads aliased near 0 and b) Basic-* 88 to 95%; Pencil 1 Sketch tapers to 0.28 but is the softest at W = 3.2512
probe_markshape.pyIs a dab round or square?Bristles-5 Flat has an aspect ratio of 2.52 and is a flat brush; mypaint cannot paint a 1 px dab at all12, 17
probe_glaze.pyWhat units does setPaintingOpacity take? What is the build-up curve? What are the width factors?it takes 0 to 1 and saturates at 255; the build-up follows 1-(1-a)^N; the factors are Airbrush 0.93, Bristles-5 0.44 and Pencil 1 Sketch 0.2012
probe_fill.pyWhich approach fills an area with a smooth edge at 8K?only a vector layer (0.083); select_mask reads 0.331 and paintPolygon is binary at 0.49812
probe_clipvector.pyCan a paint layer be clipped to a vector layer?it can, and the fractional alpha passes straight through (3 equals 1 at 0.0828)13
probe_vecstack.pyCan a group be clipped to a vector layer? How long do 15 vector layers take?it can, clipping the whole group’s composite (7,147,891, which is the silhouette); 12.3 s; a 13.8 MB .kra13
probe_vector.pyAre vector layers dependable at 8K?5 of 5: they reach the composite, beziers render, coordinates are 1:1 with zero offset, the .kra round trip works, and one shape spans 7700 px13
probe_clipping.pyDoes inherit alpha actually work?it works inside a group, clipping to the accumulated alpha; passthrough breaks it; and alpha has to be looked at13
probe_layer_order.pyWhere does addChildNode(child, None) put it?at the top; childNodes() returns bottom to top13
probe_bigcanvas.pyDoes 8192x12288 work at all?creating the document 2.3 s, reading the whole canvas 3.7 s, peak RSS 4.9 GB, and the .kra saves and opens normally18
probe_filtermask.pyDoes a filter with a selection work?apply gives a 7.2 px transition band (the floor), while FilterMask(Selection) gives 59.9 px and is non-destructive; the band is about 1.5 times the feather; and the Node overload confines nothing14
probe_colorize.pyCan ColorizeMask produce flats headless?it can: three regions matching at 0.98, 12.3 s, RSS up 1.1 GB. But its edges are binary (0.4981). initializeKeyStrokeColors has to be called first15
probe_preset_xml.pyCan a brush be made programmatically, and can the table of width factors be got rid of?toXML and fromXML work and write nothing to disk; changing diameter works on an auto brush and does nothing on a predefined tip; the factors cannot be got rid of; and a mypaint round trip is lossy by a factor of 3112
probe_scratchpad.pyCan an offscreen canvas get round the undo stack?No – there is no painting API. But it is the only way to a gradient’s or a pattern’s rasterized result, and moving that back into the document is lossless15
probe_delivery.pyWhat else can a .kra carry?a SelectionMask, a FileLayer and a CloneLayer all survive the round trip; a Palette cannot be saved; and a layer style cannot be fed in15, 16
probe_shape_edit.pyCan a vector layer still be edited after it is built?it can: setZIndex, setTransformation, createGroupShape and setAntialiased (0.2624 against 0.0891) all work. The units are points. Node transforms and a TransformMask hold up too13
probe_checkpoint.pyWhat actually eats the memory? Does save, close and reopen give it back?the same 12,000 strokes: 0.06 GB in fixed areas and 4.21 GB scattered over 8K – what grows is the tiles, not the undo stack; the round trip is faithful but returns only about 1 GB18
probe_longstroke.pyPoints are free in one paintPath call, so how should a stroke be painted?one contour is one paintPath(cubicTo) and the number of points does not affect throughput; paintLine only draws polylines12
probe_gradient_layer.pyCan headless Krita produce a gradient?a generated layer can be built but not one parameter takes effect (all five shapes look the same, the foreground and background colours do nothing, and start_position_x stays 0 after being set); there are only six keys and none of them is a stop’s colour; so the pixels have to be computed. Warning: the seexpr generator core dumps15, 16
probe_filtermask_stack.pyStacking N FilterMasks, is compositing O(n) or O(n^2)?24 on one layer is O(n^2) (1.6 s each); one each on 24 layers is O(n) (0.08 s each). The rule: at most one per layer. All 24 survive a .kra round trip14, 15
probe_bake_equiv.pyAfter changing the painting job, are the strokes still the same ones? (A reconciliation tool that never opens Krita)compare two real runs’ painting journals, aligning stroke by stroke per layer – which layer is painted first does not affect the output, so a global order that does not match is normal17, 19

Rules for writing a probe

  1. One probe, one question, and the docstring’s first line is that question
  2. the criterion goes in the docstring, including what counts as working and what it is anchored to
  3. read pixels with s.stable() or s.settle(); concluding from a single read gives the opposite answer
  4. match the probe’s geometry to the real use (a long curve against a short segment)
  5. measure inside the real structure (in a group, at 8K); a single-layer probe misses how a group composites
  6. print how to read it: [verdict] 3 clearly below 4 means ..., so that someone without the context can read the conclusion
  7. a criterion cannot be coarser than what it measures: measuring “is this edge antialiased” with a binary classifier always answers “it is binary” – which measures the classifier (probe_colorize was caught by this)
  8. a criterion needs a floor too: work out what an ideal step reads under it first (7.2 px for probe_filtermask), or “it did nothing” cannot be told from “it worked and is narrow”
  9. do not judge a translation by the centroid: a box smaller than the object cuts it off, and a group’s projection leaves a ghost. Use the object’s own boundingBox(), or measure the deviation against an analytically translated ground truth (probe_shape_edit was caught by this twice)