Save and load¶
Store new rows¶
Prefer ViveInkTool.complete for authored strokes: it keeps the captured brush and storage metadata together. Use encodeStroke when you already manage metadata, encodeHighlighter for the fixed highlighter metadata, or encodeCopy to preserve a source projection's family/version/theme settings.
val row = ViveInkCodec.encodeStroke(
stroke = finished,
id = "stroke-42",
pageId = "page-1",
seq = 42,
brushFamily = ViveBrushes.MARKER,
stabilization = 0,
colorFollowsTheme = true,
createdAt = System.currentTimeMillis(),
groupId = null,
)
val restored = ViveInkCodec.decode(row) // Stroke?, null if unreadable
Allocate IDs and seq in the database transaction. Supply family/stabilization matching the stroke's actual brush: codecs record those values and reload uses them to rebuild geometry. Persist original source rows plus operations; projections are display state.
Replay a page¶
package wiki
import com.vivenotes.byteink.kit.LoadedInkPage
import com.vivenotes.byteink.kit.StoredInkErase
import com.vivenotes.byteink.kit.StoredInkMove
import com.vivenotes.byteink.kit.StoredInkStroke
import com.vivenotes.byteink.kit.ViveInkPage
fun loadPage(
strokes: List<StoredInkStroke>,
erases: List<StoredInkErase>,
moves: List<StoredInkMove>,
): LoadedInkPage = ViveInkPage.load(
strokes = strokes,
erases = erases,
moves = moves,
)
load filters tombstones, orders strokes by seq then ID, and replays operations by createdAt then ID. It returns:
LoadedInkPage property |
Meaning |
|---|---|
strokes |
Final live projections in drawing order |
erasedAway |
Readable row IDs whose last geometry was removed; the application decides whether to tombstone them |
unreadable |
Live stroke-row IDs whose data could not be decoded |
sourceStrokes |
Decoded original rows before replay, useful for native/portable handoff |
operations |
Validated DecodedInkOperation.Erase / .Move geometry |
Run large loads away from the UI thread. executor is an optional caller-owned decode pool; loading still blocks until completion. At most four 512-row jobs are queued. onPartial receives cumulative, ordered snapshots on the load caller's thread; marshal them to the UI yourself. Partial publication runs only when there are no live move rows, because moves need global bounds/clamping.
Replay decoded ink¶
Use ViveInkPage.replay when you already hold native source strokes and decoded operations, for example after changing the active undo/redo operation set:
activeOperations contains the currently enabled DecodedInkOperation objects from loading. Replay sorts them by persisted createdAt then ID, preserves source drawing order and does not modify either supplied list. It blocks and belongs on a worker thread. Supply original pre-operation projections; replaying loaded.strokes would apply cuts and moves twice. Keep decoded operations for redo, and use load when fresh stored rows need decoding.
Row fields¶
The storage reference lists every constructor field, including required fields after defaulted parameters. Map your repository entities to these values without changing unknown fields or bytes.
| Value | Persistence behavior |
|---|---|
StoredInkStroke |
Input bytes, family/version, width/color, stabilization, bounds, grouping and tombstone |
StoredInkErase |
Mode, mask inputs/diameter, creation/undo clock and target row IDs |
StoredInkMove |
Lasso path, translation, scale/anchor, creation/undo clock and target row IDs |
deletedAt |
Non-null means deleted stroke or disabled operation |
targetIds |
Stroke-row IDs that existed when the operation was made; unrelated/newer ink stays untouched |
Stored erases/moves and their target links belong in one transaction. Undo normally toggles the operation's deletedAt; redo clears it. Keep the source stroke row so a disabled partial erase can restore its geometry through replay.
Encoding and unknown data¶
| Encoding | Data |
|---|---|
ink/androidx1 |
Android-compatible gzip/protobuf stroke or eraser inputs |
ink/lasso-f32le1 |
Little-endian point count and x/y float pairs for lasso paths |
decode, decodeErase, and decodeMove return null for unsupported or damaged data. hasValidInputData(points) validates a stroke input blob without building its mesh. The decompression cap is 64 MiB across gzip members, including trailer checks.
Unknown family IDs use the pressure-pen rendering fallback; retain the original ID. Unknown encodings/modes and damaged operations are skipped for display, with original storage left intact. Byte arrays are passed as values rather than defensively cloned by row constructors: treat them as immutable.
Use ByteInk's codec for ViveNotes rows. Calling upstream input encoding directly bypasses ByteInk's pinned Android wire compatibility and decode expansion policy.
Automatic color¶
val canvasInk = automaticInkFor(isDark = true) // white
val color = automaticColorOr(
stored = row.colorArgb,
followsTheme = row.colorFollowsTheme,
canvasInk = canvasInk,
)
true follows canvas ink; false keeps stored color; null follows canvas ink only for legacy black/white automatic colors. This is a draw override, so theme changes need no row rewrite. Highlighter color stays fixed.