Skip to content

Latest commit

 

History

History
131 lines (104 loc) · 5.81 KB

File metadata and controls

131 lines (104 loc) · 5.81 KB

Dot-pattern dilation kernels

The official Ncode SDK exposes one knob for ink-footprint shaping: isBold ∈ {false, true}, which produces either a single ink pixel per Ncode dot (false) or a 2×2 cluster (true). On real laser printers neither of these is universally optimal:

  • The 1 px dot beats the printer's halftone screen at 600 DPI and can be partially or fully dropped, depending on the engine.
  • The 2×2 cluster is a ~4× denser ink area than physically necessary, which on color RIPs drives K up enough to occlude background artwork and sometimes triggers the engine's dot-gain compensation curve in a way that shifts the centroid.

Both effects show up as drops in pen recognition rate.

This SDK adds five new dilation kernels that replace the cluster shape after the SDK emits its bitmap, leaving the dot algorithm itself untouched. Every kernel keeps its footprint within ±1 pixel of the original SDK pixel, so the dilated cluster stays inside the same Anoto cell (~9 px pitch at 600 DPI) and the pen's IR camera reads the same Ncode coordinate regardless of which kernel was used.

Tri3Up  (▲)       Tri3Down (▽)      Tri4Up           Tri4Down         Diamond4

. X .             X . X             . X .            X X X            . X .
X . X             . X .             X X X            . X .            X . X
                                                                       . X .

Anchor convention: every kernel includes (0, 0) (Diamond4 surrounds it). The center pixel is the position the SDK would have set under isBold=false.

Production default: alternating Tri3Up / Tri3Down

The recommended starting point is the alternating triangle pattern:

pdf.PublishRequest{
    Bold:            false, // SDK emits 1 px per dot
    DilationKernel:  kernels.Tri3Up,
    DilationKernelB: kernels.Tri3Down,
}

Why alternating shapes:

  • Both triangles have the same 3-pixel ink area, so total K density is predictable.
  • Alternating ▲ ▽ ▲ ▽ … in row-major discovery order means any halftone screen interaction averages across two orientations rather than resonating with one. In testing this consistently eliminated the zoom-level moiré fringe a single-orientation Tri3 sometimes produces.
  • 3 px is a single LPI cell at the typical 150 lpi office screen, so the cluster either sits inside a halftone cell (printed solid) or straddles two (printed solid+solid). Either way it survives.

When to switch to Tri4 / Diamond4

  • Tri4Up / Tri4Down: marginal printers where Tri3 occasionally drops dots. Adds one ink pixel; same alternating logic applies.
  • Diamond4: same ink count as isBold=true (4 px) but distributed along the cardinal directions, so the printed footprint reads visually lighter even though the K coverage is identical. Useful when artwork needs to remain legible behind the dots.

Generic transforms

Four building blocks in pattern/kernels:

Function Use case
ApplyKernel(bm, k) Custom shape — supply your own offsets.
ApplyAlternating(bm, kA, kB) Two shapes, row-major alternated.
ApplySquare(bm, n) Bulk up to an n×n cluster (n=2 reproduces SDK isBold).
ApplyDisc(bm, r) Circular blob of pixel radius r. Round footprint reads softer than squares.

ReduceClusterTopLefts(bm) collapses every connected zero-pixel cluster to its top-left pixel, so a Bold=true bitmap can be normalized back to 1 px/dot before applying a custom kernel.

Validating on your target printer

Pen-recognition outcomes are printer-specific: they depend on the engine's halftone screen, dot-gain curve, and toner. Treat any single data point in this document — including the recommendation to default to alternating Tri3 — as a starting hypothesis to verify on your own hardware. A practical workflow:

  1. Print one A4 page each of isBold=false raw, isBold=true (raw 2×2 cluster), Tri3Up+Tri3Down alternating, and Diamond4.
  2. Measure each page's mean K density (any imaging tool that can threshold and count black pixels works).
  3. Run pen recognition over a fixed stroke pattern on each page and record the recognition rate.
  4. Pick the kernel with the lowest density that still gives stable recognition.

NeoLAB's own reference Ncoded PDF lands around the upper end of the density range; their SDK samples are calibrated against it. Below the pen's synchronisation threshold the recognition rate falls off sharply, so the goal is to find the kernel that reaches that threshold with the least ink.

Why this is safe — footprint locality and centroid invariance

The pen does not care which pixels under a dot are inked; it computes each cluster's centroid in the IR image and quantises that to the Anoto grid (~9 px pitch at 600 DPI). Two facts make these kernels safe:

  1. Footprint locality. Every kernel offset is in [-1, 1] × [-1, 1]. The whole cluster lives within one Anoto cell of the original SDK pixel, so even an off-centre centroid quantises to the correct Ncode coordinate.
  2. Exact centroid invariance for two of the kernels:
    • Tri3Up: centroid (0, 0.667). Anti-symmetric with Tri3Down's (0, -0.667). The alternating pair averages exactly to (0, 0).
    • Diamond4: cardinal neighbours, centroid exactly (0, 0).

Tri4Up/Tri4Down are not perfect mirrors — their alternating pair centroid is (0, 0.5), a half-pixel offset that is still well inside the Anoto cell but is not exact. Use them when ink density is the priority and the half-pixel y-bias is acceptable on the target printer; otherwise prefer the alternating Tri3 pair.

TestExactCentroidInvariance and TestKernelFootprintWithinOneCell in pattern/kernels pin both properties so a future kernel addition that violates either is caught at PR time.