Skip to content

PaintNative.kt

src/jvmMain/kotlin/afield/graphics/paint/PaintNative.kt · 686 lines

@file:afield.runtime.completion.CompletionCategory("Internal")
package afield.graphics.paint

The bindings to the native field-paint library: PaintLibrary finds the library file, and the internal FFM layer under PaintCanvas calls into it.

import java.lang.foreign.Arena
import java.lang.foreign.FunctionDescriptor
import java.lang.foreign.Linker
import java.lang.foreign.MemorySegment
import java.lang.foreign.SymbolLookup
import java.lang.foreign.ValueLayout.ADDRESS
import java.lang.foreign.ValueLayout.JAVA_DOUBLE
import java.lang.foreign.ValueLayout.JAVA_FLOAT
import java.lang.foreign.ValueLayout.JAVA_INT
import java.lang.foreign.ValueLayout.JAVA_LONG
import java.lang.invoke.MethodHandle
import java.lang.ref.Cleaner
import java.nio.file.Path
import kotlin.io.path.exists

object PaintLibrary

Where native/field-paint is. Searched in order: -Dafield.paint.library, AFIELD_PAINT_LIBRARY, then build/field-paint/release in the working directory.

object PaintLibrary {
    private val os = System.getProperty("os.name").lowercase()

val fileName: String

    val fileName: String
        get() = when {
            os.contains("win") -> "field_paint_jvm.dll"
            os.contains("mac") || os.contains("darwin") -> "libfield_paint_jvm.dylib"
            else -> "libfield_paint_jvm.so"
        }

val searchedPaths: List<Path>

    val searchedPaths: List<Path>
        get() = listOfNotNull(
            System.getProperty("afield.paint.library")?.takeIf { it.isNotBlank() },
            System.getenv("AFIELD_PAINT_LIBRARY")?.takeIf { it.isNotBlank() },
        ).map(Path::of) +
            // listOf, not a bare `+ Path`: Path is Iterable<Path>, so that appends its name components.
            listOf(Path.of("build", "field-paint", "release", fileName).toAbsolutePath())

val resolvedPath: Path? get() = searchedPaths.firstOrNull { it.exists() }

Not cached: building it during a live session is picked up by the next box run.

val isAvailable: Boolean get() = resolvedPath != null

const val HOW_TO_BUILD

    const val HOW_TO_BUILD = "Build it with `./gradlew buildPaintNative` (needs cargo, and the network once)."

fun describe(): String

    fun describe(): String = resolvedPath?.let { "paint wrapper at $it (engine ${PaintNative.engine()})" }
        ?: "paint wrapper not built. $HOW_TO_BUILD"
}

internal object PaintNative

The FFM plumbing under PaintCanvas: one downcall per field_paint_* entry point in native/field-paint/src/lib.rs, whose comments are the contract. A negative status (or a null handle) means the wrapper caught an error or a panic; error fetches its message.

val cleaner: Cleaner = Cleaner.create()

class Free(private val handle: MemorySegment, private val free: (MemorySegment) -> Unit) : Runnable {

Holds only the handle, never the owner, so the owner can be collected.

override fun run() = free(handle)

source — 59 lines, from line 70
    private val linker = Linker.nativeLinker()

    private val lookup: SymbolLookup by lazy {
        val path = PaintLibrary.resolvedPath ?: throw UnsatisfiedLinkError(
            "paint wrapper is not built. ${PaintLibrary.HOW_TO_BUILD}\nLooked at: " +
                PaintLibrary.searchedPaths.joinToString(", "))
        SymbolLookup.libraryLookup(path, Arena.global())
    }

    private fun handle(name: String, d: FunctionDescriptor): MethodHandle = linker.downcallHandle(
        lookup.find(name).orElseThrow { UnsatisfiedLinkError("${PaintLibrary.fileName} has no $name; rebuild it with ./gradlew buildPaintNative") }, d)

    private fun fn(name: String, ret: java.lang.foreign.MemoryLayout?, vararg args: java.lang.foreign.MemoryLayout) = lazy {
        handle(name, if (ret == null) FunctionDescriptor.ofVoid(*args) else FunctionDescriptor.of(ret, *args))
    }

    private val F = JAVA_FLOAT
    private val I = JAVA_INT
    private val A = ADDRESS
    private val L = JAVA_LONG

    private val lastErrorH by fn("field_paint_last_error", A)
    private val engineH by fn("field_paint_engine", I)
    private val canvasNewH by fn("field_paint_canvas_new", A, I, F, I, L, F, F, F, F)
    private val canvasFreeH by fn("field_paint_canvas_free", null, A)
    private val canvasSizeH by fn("field_paint_canvas_size", null, A, A, A)
    private val canvasCopyH by fn("field_paint_canvas_copy", A, A)
    private val brushNewH by fn("field_paint_brush_new", A, I, F, F, L)
    private val brushFreeH by fn("field_paint_brush_free", null, A)
    private val brushLoadH by fn("field_paint_brush_load", I, A, F, F, F, F, F, F, F, F)
    private val brushLoadKmH by fn("field_paint_brush_load_km", I, A, F, F, F, F, F, F, F, F)
    private val brushFullnessH by fn("field_paint_brush_fullness", F, A)
    private val dragH by fn("field_paint_drag", I, A, A, A, I, F, F, F, F, F, I, A, I)
    private val touchH by fn("field_paint_touch", I, A, A, F, F, F, F, F, F, F)
    private val glazeH by fn("field_paint_glaze", I, A, A, A, I, F, F, F, F, F, F)
    private val workH by fn("field_paint_work", I, A, A, A, I, F, I, F, F, F, F, F, F, F, F, F, F, F, F, F, I, F, I, F, L)
    private val outlineH by fn("field_paint_outline", I, A, A, A, I, I, I, F, F, F, L, A, A)
    private val freeFloatsH by fn("field_paint_free_floats", null, A, L)
    private val waitH by fn("field_paint_wait", I, A, F)
    private val dryH by fn("field_paint_dry", I, A)
    private val primeH by fn("field_paint_prime", I, A, F, F, F, F, F, F, F, L)
    private val crackH by fn("field_paint_crack", I, A, I, L)
    private val softnessH by fn("field_paint_pencil_softness", F, A)
    private val drawLineH by fn("field_paint_draw_line", F, A, I, F, F, A, I, A, I, F, I, I, L)
    private val sketchLineH by fn("field_paint_sketch_line", F, A, I, F, F, A, I, F, I, F, F, L)
    private val hatchH by fn("field_paint_hatch", F, A, I, F, F, A, A, I, F, F, F, F, L)
    private val eraseH by fn("field_paint_erase", I, A, A, A, I, F, F)
    private val fixH by fn("field_paint_fix_drawing", I, A)
    private val stippleH by fn("field_paint_stipple", I, A, A, A, I, F, F, F, F, F, F, F, F, F, F, F, F, I, L)
    private val tubesH by fn("field_paint_tubes", A)
    private val freeStringH by fn("field_paint_free_string", null, A)
    private val mixTubesH by fn("field_paint_mix_tubes", I, A, A, I, F, A)
    private val mixSpectralH by fn("field_paint_mix_spectral", I, A, A, I, A)
    private val renderH by fn("field_paint_render_argb", I, A, F, F, A, I)
    private val renderWetH by fn("field_paint_render_wet_argb", I, A, A, I)
    private val statsH by fn("field_paint_stats", I, A, A)
    private val saveH by fn("field_paint_save_png", I, A, A)
    private val saveStateH by fn("field_paint_save_state", I, A, A)
    private val loadStateH by fn("field_paint_load_state", A, A)

upstream's later tools (tools.rs)

    private val D = JAVA_DOUBLE
    private val canvasNewV2H by fn("field_paint_canvas_new_v2", A, I, F, I, L, F, F, F, F, I)
    private val canvasEngineH by fn("field_paint_canvas_engine", I, A)
    private val strokeNoiseH by fn("field_paint_stroke_noise", I, A, I)
    private val strokeArcH by fn("field_paint_stroke_arc", I, A, A, I)
    private val warpH by fn("field_paint_warp", I, A, I, I, F, A, I)
    private val groundFinishH by fn("field_paint_ground_finish", I, A, F)
    private val brushNewV2H by fn("field_paint_brush_new_v2", A, I, F, L, I, A)
    private val brushConfigH by fn("field_paint_brush_config", I, A, A)
    private val brushLoadV2H by fn("field_paint_brush_load_v2", I, A, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, L)
    private val brushWipeH by fn("field_paint_brush_wipe", I, A, F)
    private val brushCarriedH by fn("field_paint_brush_carried_mm3", I, A, A, A)
    private val brushPaintH by fn("field_paint_brush_paint", I, A, A, A)
    private val spatterH by fn("field_paint_spatter", I, A, A, F, F, F, F, F, F, L)
    private val knifeNewH by fn("field_paint_knife_new", A, F)
    private val knifeFreeH by fn("field_paint_knife_free", null, A)
    private val knifeLoadH by fn("field_paint_knife_load", I, A, F, F, F, F, F, F, F, F, I, F, F, F)
    private val knifeWipeH by fn("field_paint_knife_wipe", I, A)
    private val knifeFullnessH by fn("field_paint_knife_fullness", F, A)
    private val knifePaintH by fn("field_paint_knife_paint", I, A, A, A)
    private val knifeDragH by fn("field_paint_knife_drag", I, A, A, A, I, F, F, F, I, F)
    private val ragNewH by fn("field_paint_rag_new", A, F, L)
    private val ragFreeH by fn("field_paint_rag_free", null, A)
    private val ragDipH by fn("field_paint_rag_dip", I, A, A, F)
    private val ragRefoldH by fn("field_paint_rag_refold", I, A, A)
    private val ragWipeH by fn("field_paint_rag_wipe", D, A, A, A, I, A, I, L)
    private val ragStateH by fn("field_paint_rag_state", I, A, A, A)
    private val renderLitH by fn("field_paint_render_lit_argb", I, A, F, F, F, I, A, I)
    private val statsV2H by fn("field_paint_stats_v2", I, A, A)
    private val freeBytesH by fn("field_paint_free_bytes", null, A, L)
    private val brushStateH by fn("field_paint_brush_state", I, A, A, A)
    private val brushFromStateH by fn("field_paint_brush_from_state", A, A, L)
    private val knifeStateH by fn("field_paint_knife_state", I, A, A, A)
    private val knifeFromStateH by fn("field_paint_knife_from_state", A, A, L)
    private val ragStateBytesH by fn("field_paint_rag_state_bytes", I, A, A, A)
    private val ragFromStateH by fn("field_paint_rag_from_state", A, A, L)

water (water.rs)

    private val canvasNewPaperH by fn("field_paint_canvas_new_paper", A, I, F, A, L, F, I)
    private val mmPerUnitH by fn("field_paint_canvas_mm_per_unit", F, A)
    private val paperH by fn("field_paint_paper", I, A, A)
    private val tiltH by fn("field_paint_tilt", I, A, F, F, L)
    private val evaporationH by fn("field_paint_evaporation", I, A, F, L)
    private val waitSecondsH by fn("field_paint_wait_seconds", I, A, F)
    private val dispersionH by fn("field_paint_dispersion", I, A, F, L)
    private val marangoniH by fn("field_paint_marangoni", I, A, F, L)
    private val wbrushNewH by fn("field_paint_wbrush_new", A, A, F, F)
    private val wbrushFreeH by fn("field_paint_wbrush_free", null, A)
    private val wbrushLoadH by fn("field_paint_wbrush_load", I, A, A, A, A, I, F, F)
    private val wbrushBlotH by fn("field_paint_wbrush_blot", I, A, F)
    private val wbrushStateH by fn("field_paint_wbrush_state", I, A, A)
    private val wbrushStrokeH by fn("field_paint_wbrush_stroke", I, A, A, A, I, L, A)
    private val wbrushStateBytesH by fn("field_paint_wbrush_state_bytes", I, A, A, A)
    private val wbrushFromStateH by fn("field_paint_wbrush_from_state", A, A, L)
    private val waterDropH by fn("field_paint_water_drop", I, A, F, F, F, A, A, A, I, L)
    private val waterWashH by fn("field_paint_water_wash", I, A, A, A, I, F, F, A, A, A, I, L)
    private val waterStatsH by fn("field_paint_water_stats", I, A, A, I)
    private val waterSpeciesH by fn("field_paint_water_species", A, A)
    private val waterMapH by fn("field_paint_water_map", I, A, I, A, I)
    private val aqPigmentH by fn("field_paint_aq_pigment", I, A, A)
    private val aqPigmentNamesH by fn("field_paint_aq_pigment_names", A)
    private val aqPigmentMakeH by fn("field_paint_aq_pigment_make", I, F, F, F, F, F, F, F, F, F, A)

the segmented brush (dynamic.rs)

    private val dbrushNewH by fn("field_paint_dbrush_new", A, A, I, F, A, I, I, L)
    private val dbrushFreeH by fn("field_paint_dbrush_free", null, A)
    private val dbrushSetH by fn("field_paint_dbrush_set", I, A, F, F, F, F, F, F)
    private val dbrushParamH by fn("field_paint_dbrush_param", I, A, A, F)
    private val dbrushVaryH by fn("field_paint_dbrush_vary", I, A, A, A, I)
    private val dbrushInfoH by fn("field_paint_dbrush_info", I, A, A)
    private val dbrushLoadH by fn("field_paint_dbrush_load", I, A, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, L)
    private val dbrushLoadWaterH by fn("field_paint_dbrush_load_water", I, A, A, A, A, A, I, F, F)
    private val dbrushStrokeH by fn("field_paint_dbrush_stroke", I, A, A, A, I, L, A)
    private val dbrushHairsH by fn("field_paint_dbrush_hairs", I, A, A, A, A)
    private val contactMapH by fn("field_paint_contact_map", I, A, A, I)
    private val dbrushStateH by fn("field_paint_dbrush_state", I, A, A, A)
    private val dbrushFromStateH by fn("field_paint_dbrush_from_state", A, A, L)

    private fun error(what: String): Nothing {
        val msg = (lastErrorH.invokeExact() as MemorySegment).reinterpret(Long.MAX_VALUE).getString(0)
        throw IllegalStateException("paint: $what failed: $msg")
    }

    private fun check(status: Int, what: String) { if (status < 0) error(what) }
    private fun check(drawn: Float, what: String): Double = if (drawn < 0f) error(what) else drawn.toDouble()
    private fun nonNull(p: MemorySegment, what: String): MemorySegment = if (p == MemorySegment.NULL) error(what) else p

Rings packed as the wrapper's regions want them: all points, then each ring's size.

    private fun Arena.rings(rings: List<FloatArray>?): Triple<MemorySegment, MemorySegment, Int> {
        if (rings.isNullOrEmpty()) return Triple(MemorySegment.NULL, MemorySegment.NULL, 0)
        val xy = FloatArray(rings.sumOf { it.size })
        var at = 0
        for (r in rings) { r.copyInto(xy, at); at += r.size }
        return Triple(allocateFrom(F, *xy), allocateFrom(I, *IntArray(rings.size) { rings[it].size / 2 }), rings.size)
    }

fun engine(): Int = engineH.invokeExact() as Int

fun canvasNew(w: Int, aspect: Float, style: Int, seed: Long, r: Float, g: Float, b: Float, sizeMm: Float): MemorySegment

    fun canvasNew(w: Int, aspect: Float, style: Int, seed: Long, r: Float, g: Float, b: Float, sizeMm: Float): MemorySegment =
        nonNull(canvasNewH.invokeExact(w, aspect, style, seed, r, g, b, sizeMm) as MemorySegment, "creating a canvas")

fun canvasFree(c: MemorySegment) { canvasFreeH.invokeExact(c) }

fun canvasCopy(c: MemorySegment): MemorySegment

    fun canvasCopy(c: MemorySegment): MemorySegment = nonNull(canvasCopyH.invokeExact(c) as MemorySegment, "copying the canvas")

fun canvasSize(c: MemorySegment): Pair<Int, Int>

    fun canvasSize(c: MemorySegment): Pair<Int, Int> = Arena.ofConfined().use { a ->
        val w = a.allocate(I)
        val h = a.allocate(I)
        canvasSizeH.invokeExact(c, w, h)
        w.get(I, 0) to h.get(I, 0)
    }

fun brushNew(kind: Int, width: Float, point: Float, seed: Long): MemorySegment

    fun brushNew(kind: Int, width: Float, point: Float, seed: Long): MemorySegment =
        nonNull(brushNewH.invokeExact(kind, width, point, seed) as MemorySegment, "creating a brush")

fun brushFree(b: MemorySegment) { brushFreeH.invokeExact(b) }

fun brushLoad(b: MemorySegment, r: Float, g: Float, bl: Float, hiding: Float, stiff: Float, drying: Float, amount: Float, wipe: Float)

    fun brushLoad(b: MemorySegment, r: Float, g: Float, bl: Float, hiding: Float, stiff: Float, drying: Float, amount: Float, wipe: Float) =
        check(brushLoadH.invokeExact(b, r, g, bl, hiding, stiff, drying, amount, wipe) as Int, "loading the brush")

fun brushLoadKm(b: MemorySegment, r: Float, g: Float, bl: Float, scatter: Float, stiff: Float, drying: Float, amount: Float, wipe: Float)

    fun brushLoadKm(b: MemorySegment, r: Float, g: Float, bl: Float, scatter: Float, stiff: Float, drying: Float, amount: Float, wipe: Float) =
        check(brushLoadKmH.invokeExact(b, r, g, bl, scatter, stiff, drying, amount, wipe) as Int, "loading the brush")

fun brushFullness(b: MemorySegment): Float = brushFullnessH.invokeExact(b) as Float

fun drag(c: MemorySegment, b: MemorySegment, xy: FloatArray, p0: Float, p1: Float, shake: Float,

    fun drag(c: MemorySegment, b: MemorySegment, xy: FloatArray, p0: Float, p1: Float, shake: Float,
             attack: Float, release: Float, along: Boolean, swell: List<Double>) = Arena.ofConfined().use { a ->
        val sw = if (swell.isEmpty()) MemorySegment.NULL else a.allocateFrom(F, *FloatArray(swell.size) { swell[it].toFloat() })
        check(dragH.invokeExact(c, b, a.allocateFrom(F, *xy), xy.size / 2, p0, p1, shake, attack, release,
            if (along) 1 else 0, sw, swell.size) as Int, "a stroke")
    }

fun touch(c: MemorySegment, b: MemorySegment, x: Float, y: Float, p: Float, dx: Float, dy: Float, twist: Float, angle: Float)

    fun touch(c: MemorySegment, b: MemorySegment, x: Float, y: Float, p: Float, dx: Float, dy: Float, twist: Float, angle: Float) =
        check(touchH.invokeExact(c, b, x, y, p, dx, dy, twist, angle) as Int, "a touch")

fun glaze(c: MemorySegment, rings: List<FloatArray>?, feather: Float, r: Float, g: Float, b: Float, hiding: Float, coats: Float)

    fun glaze(c: MemorySegment, rings: List<FloatArray>?, feather: Float, r: Float, g: Float, b: Float, hiding: Float, coats: Float) =
        Arena.ofConfined().use { a ->
            val (xy, sizes, n) = a.rings(rings)
            check(glazeH.invokeExact(c, xy, sizes, n, feather, r, g, b, hiding, coats) as Int, "a glaze")
        }

fun work(c: MemorySegment, rings: List<FloatArray>, feather: Float, preset: Int, r: Float, g: Float, b: Float,

    fun work(c: MemorySegment, rings: List<FloatArray>, feather: Float, preset: Int, r: Float, g: Float, b: Float,
             hiding: Float, stiff: Float, angle: Float, angleJitter: Float, len0: Float, len1: Float, coverage: Float,
             p0: Float, p1: Float, cross: Float, order: Int, sweep: Float, clip: Int, width: Float, seed: Long) =
        Arena.ofConfined().use { a ->
            val (xy, sizes, n) = a.rings(rings)
            check(workH.invokeExact(c, xy, sizes, n, feather, preset, r, g, b, hiding, stiff, angle, angleJitter,
                len0, len1, coverage, p0, p1, cross, order, sweep, clip, width, seed) as Int, "working a region")
        }

fun outline(c: MemorySegment, b: MemorySegment?, xy: FloatArray, closed: Boolean, character: Int, amount: Float,

The outline's strokes, each as interleaved canvas-unit x, y.

    fun outline(c: MemorySegment, b: MemorySegment?, xy: FloatArray, closed: Boolean, character: Int, amount: Float,
                pressure: Float, size: Float, seed: Long): List<FloatArray> = Arena.ofConfined().use { a ->
        val out = a.allocate(A)
        val len = a.allocate(L)
        check(outlineH.invokeExact(c, b ?: MemorySegment.NULL, a.allocateFrom(F, *xy), xy.size / 2, if (closed) 1 else 0,
            character, amount, pressure, size, seed, out, len) as Int, "an outline")
        val n = len.get(L, 0)
        val buf = out.get(A, 0).reinterpret(n * 4)
        try {
            val v = buf.toArray(F)
            val count = v[0].toInt()
            var at = 1 + count
            List(count) { i ->
                val k = 2 * v[1 + i].toInt()
                v.copyOfRange(at, at + k).also { at += k }
            }
        } finally {
            freeFloatsH.invokeExact(buf, n)
        }
    }

fun waitMinutes(c: MemorySegment, minutes: Float)

    fun waitMinutes(c: MemorySegment, minutes: Float) = check(waitH.invokeExact(c, minutes) as Int, "wait")

fun dry(c: MemorySegment) = check(dryH.invokeExact(c) as Int, "dry")

fun prime(c: MemorySegment, r: Float, g: Float, b: Float, hiding: Float, um: Float, stiff: Float, texture: Float, seed: Long)

    fun prime(c: MemorySegment, r: Float, g: Float, b: Float, hiding: Float, um: Float, stiff: Float, texture: Float, seed: Long) =
        check(primeH.invokeExact(c, r, g, b, hiding, um, stiff, texture, seed) as Int, "priming")

fun crack(c: MemorySegment, kind: Int, seed: Long)

    fun crack(c: MemorySegment, kind: Int, seed: Long) = check(crackH.invokeExact(c, kind, seed) as Int, "craquelure")

fun pencilSoftness(grade: String): Double

    fun pencilSoftness(grade: String): Double = Arena.ofConfined().use { a ->
        val s = softnessH.invokeExact(a.allocateFrom(grade)) as Float
        require(!s.isNaN()) { "\"$grade\" is not a pencil grade; try 9H .. H, F, HB, B .. 9B" }
        s.toDouble()
    }

    private fun Arena.floats(v: List<Double>): MemorySegment = allocateFrom(F, *FloatArray(v.size) { v[it].toFloat() })

fun drawLine(c: MemorySegment, p: Pencil, xy: FloatArray, profile: List<Double>, tremor: Float, smooth: Boolean, ruler: Boolean, seed: Long): Double

    fun drawLine(c: MemorySegment, p: Pencil, xy: FloatArray, profile: List<Double>, tremor: Float, smooth: Boolean, ruler: Boolean, seed: Long): Double =
        Arena.ofConfined().use { a ->
            check(drawLineH.invokeExact(c, p.medium.code, p.softness.toFloat(), p.worn.toFloat(), a.allocateFrom(F, *xy), xy.size / 2,
                a.floats(profile), profile.size, tremor, if (smooth) 1 else 0, if (ruler) 1 else 0, seed) as Float, "drawing a line")
        }

fun sketchLine(c: MemorySegment, p: Pencil, xy: FloatArray, pressure: Float, passes: Int, wander: Float, tremor: Float, seed: Long): Double

    fun sketchLine(c: MemorySegment, p: Pencil, xy: FloatArray, pressure: Float, passes: Int, wander: Float, tremor: Float, seed: Long): Double =
        Arena.ofConfined().use { a ->
            check(sketchLineH.invokeExact(c, p.medium.code, p.softness.toFloat(), p.worn.toFloat(), a.allocateFrom(F, *xy), xy.size / 2,
                pressure, passes, wander, tremor, seed) as Float, "sketching a line")
        }

fun hatch(c: MemorySegment, p: Pencil, rings: List<FloatArray>, angle: Float, spacing: Float, length: Float, pressure: Float, seed: Long): Double

    fun hatch(c: MemorySegment, p: Pencil, rings: List<FloatArray>, angle: Float, spacing: Float, length: Float, pressure: Float, seed: Long): Double =
        Arena.ofConfined().use { a ->
            val (xy, sizes, n) = a.rings(rings)
            check(hatchH.invokeExact(c, p.medium.code, p.softness.toFloat(), p.worn.toFloat(), xy, sizes, n,
                angle, spacing, length, pressure, seed) as Float, "hatching")
        }

fun erase(c: MemorySegment, rings: List<FloatArray>, feather: Float, strength: Float)

    fun erase(c: MemorySegment, rings: List<FloatArray>, feather: Float, strength: Float) = Arena.ofConfined().use { a ->
        val (xy, sizes, n) = a.rings(rings)
        check(eraseH.invokeExact(c, xy, sizes, n, feather, strength) as Int, "erasing")
    }

fun fixDrawing(c: MemorySegment) = check(fixH.invokeExact(c) as Int, "fixative")

fun stipple(c: MemorySegment, rings: List<FloatArray>, feather: Float, size: Float, coverage: Float, r: Float, g: Float, b: Float,

    fun stipple(c: MemorySegment, rings: List<FloatArray>, feather: Float, size: Float, coverage: Float, r: Float, g: Float, b: Float,
                hiding: Float, stiff: Float, p0: Float, p1: Float, cluster: Float, drag: Float, dipEvery: Int, seed: Long) =
        Arena.ofConfined().use { a ->
            val (xy, sizes, n) = a.rings(rings)
            check(stippleH.invokeExact(c, xy, sizes, n, feather, size, coverage, r, g, b, hiding, stiff, p0, p1,
                cluster, drag, dipEvery, seed) as Int, "stippling")
        }

fun tubes(): String

    fun tubes(): String {
        val p = nonNull(tubesH.invokeExact() as MemorySegment, "listing tubes")
        try {
            return p.reinterpret(Long.MAX_VALUE).getString(0)
        } finally {
            freeStringH.invokeExact(p)
        }
    }

fun mixTubes(tubes: Map<String, Double>, medium: Float): FloatArray

[r, g, b (linear), scatter, stiff, drying] of the pile.

    fun mixTubes(tubes: Map<String, Double>, medium: Float): FloatArray = Arena.ofConfined().use { a ->
        require(tubes.isNotEmpty()) { "mix at least one tube" }
        require(tubes.keys.none { '\n' in it }) { "tube names cannot contain newlines" }
        val out = a.allocate(F, 6)
        check(mixTubesH.invokeExact(a.allocateFrom(tubes.keys.joinToString("\n")), a.floats(tubes.values.toList()),
            tubes.size, medium, out) as Int, "mixing tubes")
        out.toArray(F)
    }

fun mixSpectral(rgb: FloatArray, weights: FloatArray): FloatArray

    fun mixSpectral(rgb: FloatArray, weights: FloatArray): FloatArray = Arena.ofConfined().use { a ->
        val out = a.allocate(F, 3)
        check(mixSpectralH.invokeExact(a.allocateFrom(F, *rgb), a.allocateFrom(F, *weights), weights.size, out) as Int, "spectral mix")
        out.toArray(F)
    }

fun renderArgb(c: MemorySegment, pixels: Int, relief: Float, gloss: Float): IntArray

    fun renderArgb(c: MemorySegment, pixels: Int, relief: Float, gloss: Float): IntArray = Arena.ofConfined().use { a ->
        val out = a.allocate(I, pixels.toLong())
        check(renderH.invokeExact(c, relief, gloss, out, pixels) as Int, "render")
        out.toArray(I)
    }

fun renderWetArgb(c: MemorySegment, pixels: Int): IntArray

    fun renderWetArgb(c: MemorySegment, pixels: Int): IntArray = Arena.ofConfined().use { a ->
        val out = a.allocate(I, pixels.toLong())
        check(renderWetH.invokeExact(c, out, pixels) as Int, "render")
        out.toArray(I)
    }

fun stats(c: MemorySegment): PaintStats

    fun stats(c: MemorySegment): PaintStats = Arena.ofConfined().use { a ->
        val out = a.allocate(JAVA_DOUBLE, 6)
        check(statsH.invokeExact(c, out) as Int, "stats")
        val v = out.toArray(JAVA_DOUBLE)
        PaintStats(v[0], v[1], v[2], v[3], v[4], v[5])
    }

fun savePng(c: MemorySegment, path: String)

    fun savePng(c: MemorySegment, path: String) = Arena.ofConfined().use { a ->
        check(saveH.invokeExact(c, a.allocateFrom(path)) as Int, "saving $path")
    }

fun saveState(c: MemorySegment, path: String)

    fun saveState(c: MemorySegment, path: String) = Arena.ofConfined().use { a ->
        check(saveStateH.invokeExact(c, a.allocateFrom(path)) as Int, "saving $path")
    }

fun loadState(path: String): MemorySegment

    fun loadState(path: String): MemorySegment = Arena.ofConfined().use { a ->
        nonNull(loadStateH.invokeExact(a.allocateFrom(path)) as MemorySegment, "loading $path")
    }

upstream's later tools

fun canvasNewV2(w: Int, aspect: Float, style: Int, seed: Long, r: Float, g: Float, b: Float, sizeMm: Float, engine: Int): MemorySegment

    fun canvasNewV2(w: Int, aspect: Float, style: Int, seed: Long, r: Float, g: Float, b: Float, sizeMm: Float, engine: Int): MemorySegment =
        nonNull(canvasNewV2H.invokeExact(w, aspect, style, seed, r, g, b, sizeMm, engine) as MemorySegment, "creating a canvas")

fun strokeNoise(c: MemorySegment, noise: Int)

    fun strokeNoise(c: MemorySegment, noise: Int) = check(strokeNoiseH.invokeExact(c, noise) as Int, "keying a stroke's noise")

fun warp(c: MemorySegment, columns: Int, rows: Int, cell: Float, at: FloatArray?, ground: Boolean)

    fun warp(c: MemorySegment, columns: Int, rows: Int, cell: Float, at: FloatArray?, ground: Boolean) = Arena.ofConfined().use { a ->
        check(warpH.invokeExact(c, columns, rows, cell, at?.let { a.allocateFrom(F, *it) } ?: MemorySegment.NULL, if (ground) 1 else 0) as Int, "warping the canvas")
    }

fun strokeArc(c: MemorySegment, arcMm: FloatArray)

    fun strokeArc(c: MemorySegment, arcMm: FloatArray) = Arena.ofConfined().use { a ->
        check(strokeArcH.invokeExact(c, a.allocateFrom(F, *arcMm), arcMm.size) as Int, "giving a stroke its length")
    }

fun canvasEngine(c: MemorySegment): Int

    fun canvasEngine(c: MemorySegment): Int = (canvasEngineH.invokeExact(c) as Int).also { if (it < 0) error("reading the engine") }

fun groundFinish(c: MemorySegment, absorbent: Float)

    fun groundFinish(c: MemorySegment, absorbent: Float) = check(groundFinishH.invokeExact(c, absorbent) as Int, "finishing the ground")

fun brushNewV2(kind: Int, width: Float, seed: Long, engine: Int, cfg: FloatArray): MemorySegment

cfg is the 11 construction values of field_paint_brush_new_v2, NaN for the preset's.

    fun brushNewV2(kind: Int, width: Float, seed: Long, engine: Int, cfg: FloatArray): MemorySegment = Arena.ofConfined().use { a ->
        require(cfg.size == 12)
        nonNull(brushNewV2H.invokeExact(kind, width, seed, engine, a.allocateFrom(F, *cfg)) as MemorySegment, "creating a brush")
    }

fun brushConfig(b: MemorySegment): FloatArray

kind, width, bristles, length, stiffness, hair, run, lay, pickup, push, splay, ragged, point, tooth.

    fun brushConfig(b: MemorySegment): FloatArray = Arena.ofConfined().use { a ->
        val out = a.allocate(F, 14)
        check(brushConfigH.invokeExact(b, out) as Int, "reading the brush")
        out.toArray(F)
    }

fun brushLoadV2(b: MemorySegment, r: Float, g: Float, bl: Float, hiding: Float, scatter: Float, stiff: Float, drying: Float,

    fun brushLoadV2(b: MemorySegment, r: Float, g: Float, bl: Float, hiding: Float, scatter: Float, stiff: Float, drying: Float,
                    amount: Float, wipe: Float, thinner: Float, turps: Float, oil: Float, side: Float, share: Float, streak: Float, partSeed: Long) =
        check(brushLoadV2H.invokeExact(b, r, g, bl, hiding, scatter, stiff, drying, amount, wipe, thinner, turps, oil,
            side, share, streak, partSeed) as Int, "loading the brush")

fun brushWipe(b: MemorySegment, fraction: Float)

    fun brushWipe(b: MemorySegment, fraction: Float) = check(brushWipeH.invokeExact(b, fraction) as Int, "wiping the brush")

fun brushCarried(c: MemorySegment, b: MemorySegment): DoubleArray

    fun brushCarried(c: MemorySegment, b: MemorySegment): DoubleArray = Arena.ofConfined().use { a ->
        val out = a.allocate(D, 2)
        check(brushCarriedH.invokeExact(c, b, out) as Int, "measuring the brush")
        out.toArray(D)
    }

fun brushPaint(c: MemorySegment, b: MemorySegment): FloatArray?

[r, g, b (linear), mm³] of the paint in the brush, or null when it is clean.

    fun brushPaint(c: MemorySegment, b: MemorySegment): FloatArray? = Arena.ofConfined().use { a ->
        val out = a.allocate(F, 4)
        val st = brushPaintH.invokeExact(c, b, out) as Int
        check(st, "reading the brush's paint")
        if (st == 0) null else out.toArray(F)
    }

fun spatter(c: MemorySegment, b: MemorySegment, x: Float, y: Float, tx: Float, ty: Float, spread: Float, force: Float, seed: Long): Int

    fun spatter(c: MemorySegment, b: MemorySegment, x: Float, y: Float, tx: Float, ty: Float, spread: Float, force: Float, seed: Long): Int =
        (spatterH.invokeExact(c, b, x, y, tx, ty, spread, force, seed) as Int).also { check(it, "spatter") }

fun knifeNew(width: Float): MemorySegment

    fun knifeNew(width: Float): MemorySegment = nonNull(knifeNewH.invokeExact(width) as MemorySegment, "making a knife")

fun knifeFree(k: MemorySegment) { knifeFreeH.invokeExact(k) }

fun knifeLoad(k: MemorySegment, r: Float, g: Float, b: Float, hiding: Float, scatter: Float, stiff: Float, drying: Float,

    fun knifeLoad(k: MemorySegment, r: Float, g: Float, b: Float, hiding: Float, scatter: Float, stiff: Float, drying: Float,
                  amount: Float, wipe: Boolean, thinner: Float, turps: Float, oil: Float) =
        check(knifeLoadH.invokeExact(k, r, g, b, hiding, scatter, stiff, drying, amount, if (wipe) 1 else 0, thinner, turps, oil) as Int, "loading the knife")

fun knifeWipe(k: MemorySegment) = check(knifeWipeH.invokeExact(k) as Int, "wiping the knife")

fun knifeFullness(k: MemorySegment): Float = knifeFullnessH.invokeExact(k) as Float

fun knifePaint(c: MemorySegment, k: MemorySegment): FloatArray?

    fun knifePaint(c: MemorySegment, k: MemorySegment): FloatArray? = Arena.ofConfined().use { a ->
        val out = a.allocate(F, 4)
        val st = knifePaintH.invokeExact(c, k, out) as Int
        check(st, "reading the knife's paint")
        if (st == 0) null else out.toArray(F)
    }

fun knifeDrag(c: MemorySegment, k: MemorySegment, xy: FloatArray, p0: Float, p1: Float, angle: Float, lay: Boolean, lift: Float)

    fun knifeDrag(c: MemorySegment, k: MemorySegment, xy: FloatArray, p0: Float, p1: Float, angle: Float, lay: Boolean, lift: Float) =
        Arena.ofConfined().use { a ->
            check(knifeDragH.invokeExact(c, k, a.allocateFrom(F, *xy), xy.size / 2, p0, p1, angle, if (lay) 1 else 0, lift) as Int, "a knife stroke")
        }

fun ragNew(width: Float, seed: Long): MemorySegment

    fun ragNew(width: Float, seed: Long): MemorySegment = nonNull(ragNewH.invokeExact(width, seed) as MemorySegment, "making a rag")

fun ragFree(g: MemorySegment) { ragFreeH.invokeExact(g) }

fun ragDip(c: MemorySegment, g: MemorySegment, amount: Float)

    fun ragDip(c: MemorySegment, g: MemorySegment, amount: Float) = check(ragDipH.invokeExact(c, g, amount) as Int, "dipping the rag")

fun ragRefold(c: MemorySegment, g: MemorySegment)

    fun ragRefold(c: MemorySegment, g: MemorySegment) = check(ragRefoldH.invokeExact(c, g) as Int, "refolding the rag")

fun ragWipe(c: MemorySegment, g: MemorySegment, xy: FloatArray, pressure: List<Double>, seed: Long): Double

    fun ragWipe(c: MemorySegment, g: MemorySegment, xy: FloatArray, pressure: List<Double>, seed: Long): Double = Arena.ofConfined().use { a ->
        val pr = if (pressure.isEmpty()) MemorySegment.NULL else a.floats(pressure)
        val got = ragWipeH.invokeExact(c, g, a.allocateFrom(F, *xy), xy.size / 2, pr, pressure.size, seed) as Double
        if (got.isNaN()) error("a wipe") else got
    }

fun ragState(c: MemorySegment, g: MemorySegment): FloatArray

load, soaked, damp.

    fun ragState(c: MemorySegment, g: MemorySegment): FloatArray = Arena.ofConfined().use { a ->
        val out = a.allocate(F, 3)
        check(ragStateH.invokeExact(c, g, out) as Int, "reading the rag")
        out.toArray(F)
    }

fun renderLitArgb(c: MemorySegment, pixels: Int, azimuth: Float, elevation: Float, strength: Float, dried: Boolean): IntArray

    fun renderLitArgb(c: MemorySegment, pixels: Int, azimuth: Float, elevation: Float, strength: Float, dried: Boolean): IntArray =
        Arena.ofConfined().use { a ->
            val out = a.allocate(I, pixels.toLong())
            check(renderLitH.invokeExact(c, azimuth, elevation, strength, if (dried) 1 else 0, out, pixels) as Int, "render")
            out.toArray(I)
        }

fun statsV2(c: MemorySegment): PaintStats

    fun statsV2(c: MemorySegment): PaintStats = Arena.ofConfined().use { a ->
        val out = a.allocate(D, 9)
        check(statsV2H.invokeExact(c, out) as Int, "stats")
        val v = out.toArray(D)
        PaintStats(v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[8].toInt())
    }

    private fun bytes(what: String, call: (MemorySegment, MemorySegment) -> Int): ByteArray = Arena.ofConfined().use { a ->
        val out = a.allocate(A)
        val len = a.allocate(L)
        check(call(out, len), what)
        val n = len.get(L, 0)
        val buf = out.get(A, 0).reinterpret(n)
        try { buf.toArray(java.lang.foreign.ValueLayout.JAVA_BYTE) } finally { freeBytesH.invokeExact(buf, n) }
    }

    private fun fromBytes(bytes: ByteArray, what: String, call: (MemorySegment, Long) -> MemorySegment): MemorySegment = Arena.ofConfined().use { a ->
        nonNull(call(a.allocateFrom(java.lang.foreign.ValueLayout.JAVA_BYTE, *bytes), bytes.size.toLong()), what)
    }

fun brushState(b: MemorySegment): ByteArray

    fun brushState(b: MemorySegment): ByteArray = bytes("saving the brush") { o, l -> brushStateH.invokeExact(b, o, l) as Int }

fun brushFromState(bytes: ByteArray): MemorySegment

    fun brushFromState(bytes: ByteArray): MemorySegment = fromBytes(bytes, "restoring a brush") { p, n -> brushFromStateH.invokeExact(p, n) as MemorySegment }

fun knifeState(k: MemorySegment): ByteArray

    fun knifeState(k: MemorySegment): ByteArray = bytes("saving the knife") { o, l -> knifeStateH.invokeExact(k, o, l) as Int }

fun knifeFromState(bytes: ByteArray): MemorySegment

    fun knifeFromState(bytes: ByteArray): MemorySegment = fromBytes(bytes, "restoring a knife") { p, n -> knifeFromStateH.invokeExact(p, n) as MemorySegment }

fun ragStateBytes(g: MemorySegment): ByteArray

    fun ragStateBytes(g: MemorySegment): ByteArray = bytes("saving the rag") { o, l -> ragStateBytesH.invokeExact(g, o, l) as Int }

fun ragFromState(bytes: ByteArray): MemorySegment

    fun ragFromState(bytes: ByteArray): MemorySegment = fromBytes(bytes, "restoring a rag") { p, n -> ragFromStateH.invokeExact(p, n) as MemorySegment }

water

fun canvasNewPaper(w: Int, aspect: Float, paper: String, seed: Long, sizeMm: Float, engine: Int): MemorySegment

    fun canvasNewPaper(w: Int, aspect: Float, paper: String, seed: Long, sizeMm: Float, engine: Int): MemorySegment = Arena.ofConfined().use { a ->
        nonNull(canvasNewPaperH.invokeExact(w, aspect, a.allocateFrom(paper), seed, sizeMm, engine) as MemorySegment, "making a sheet of $paper")
    }

fun mmPerUnit(c: MemorySegment): Float = mmPerUnitH.invokeExact(c) as Float

fun paper(c: MemorySegment): FloatArray?

The paper's 12 values, or null when the canvas has no water.

    fun paper(c: MemorySegment): FloatArray? = Arena.ofConfined().use { a ->
        val out = a.allocate(F, 12)
        val st = paperH.invokeExact(c, out) as Int
        check(st, "reading the paper")
        if (st == 0) null else out.toArray(F)
    }

fun tilt(c: MemorySegment, degrees: Float, direction: Float, seed: Long)

    fun tilt(c: MemorySegment, degrees: Float, direction: Float, seed: Long) = check(tiltH.invokeExact(c, degrees, direction, seed) as Int, "tilting")

fun evaporation(c: MemorySegment, umS: Float, seed: Long)

    fun evaporation(c: MemorySegment, umS: Float, seed: Long) = check(evaporationH.invokeExact(c, umS, seed) as Int, "setting evaporation")

fun dispersion(c: MemorySegment, v: Float, seed: Long)

    fun dispersion(c: MemorySegment, v: Float, seed: Long) = check(dispersionH.invokeExact(c, v, seed) as Int, "setting dispersion")

fun marangoni(c: MemorySegment, v: Float, seed: Long)

    fun marangoni(c: MemorySegment, v: Float, seed: Long) = check(marangoniH.invokeExact(c, v, seed) as Int, "setting the surface tension drop")

fun waitSeconds(c: MemorySegment, s: Float)

    fun waitSeconds(c: MemorySegment, s: Float) = check(waitSecondsH.invokeExact(c, s) as Int, "waiting")

fun wbrushNew(c: MemorySegment, width: Float, capacity: Float): MemorySegment

    fun wbrushNew(c: MemorySegment, width: Float, capacity: Float): MemorySegment = nonNull(wbrushNewH.invokeExact(c, width, capacity) as MemorySegment, "making a watercolour brush")

fun wbrushFree(b: MemorySegment) { wbrushFreeH.invokeExact(b) }

    private class Pig(val names: MemorySegment, val params: MemorySegment, val conc: MemorySegment, val n: Int)
    private fun Arena.pig(paint: WaterPaint): Pig {
        val ps = paint.pigments.entries.toList()
        if (ps.isEmpty()) return Pig(allocateFrom(""), MemorySegment.NULL, MemorySegment.NULL, 0)
        require(ps.none { '\n' in it.key.name }) { "pigment names cannot contain newlines" }
        val params = FloatArray(8 * ps.size)
        ps.forEachIndexed { i, e -> e.key.params().copyInto(params, 8 * i) }
        return Pig(allocateFrom(ps.joinToString("\n") { it.key.name }), allocateFrom(F, *params),
            allocateFrom(F, *FloatArray(ps.size) { ps[it].value.toFloat() }), ps.size)
    }

fun wbrushLoad(b: MemorySegment, paint: WaterPaint, fill: Float, wipe: Float)

    fun wbrushLoad(b: MemorySegment, paint: WaterPaint, fill: Float, wipe: Float) = Arena.ofConfined().use { a ->
        val p = a.pig(paint)
        check(wbrushLoadH.invokeExact(b, p.names, p.params, p.conc, p.n, fill, wipe) as Int, "loading the brush")
    }

fun wbrushBlot(b: MemorySegment, share: Float)

    fun wbrushBlot(b: MemorySegment, share: Float) = check(wbrushBlotH.invokeExact(b, share) as Int, "blotting the brush")

fun wbrushState(b: MemorySegment): FloatArray

    fun wbrushState(b: MemorySegment): FloatArray = Arena.ofConfined().use { a ->
        val out = a.allocate(F, 4)
        check(wbrushStateH.invokeExact(b, out) as Int, "reading the brush")
        out.toArray(F)
    }

fun wbrushStroke(c: MemorySegment, b: MemorySegment, xytp: FloatArray, seed: Long): DoubleArray

xytp is x, y, t, pressure per sample; returns laid mm³, taken mm³, laid µg, taken µg, seconds.

    fun wbrushStroke(c: MemorySegment, b: MemorySegment, xytp: FloatArray, seed: Long): DoubleArray = Arena.ofConfined().use { a ->
        val out = a.allocate(D, 5)
        check(wbrushStrokeH.invokeExact(c, b, a.allocateFrom(F, *xytp), xytp.size / 4, seed, out) as Int, "a watercolour stroke")
        out.toArray(D)
    }

fun wbrushStateBytes(b: MemorySegment): ByteArray

    fun wbrushStateBytes(b: MemorySegment): ByteArray = bytes("saving the brush") { o, l -> wbrushStateBytesH.invokeExact(b, o, l) as Int }

fun wbrushFromState(bytes: ByteArray): MemorySegment

    fun wbrushFromState(bytes: ByteArray): MemorySegment = fromBytes(bytes, "restoring a watercolour brush") { p, n -> wbrushFromStateH.invokeExact(p, n) as MemorySegment }

fun waterDrop(c: MemorySegment, x: Float, y: Float, volume: Float, paint: WaterPaint, seed: Long)

    fun waterDrop(c: MemorySegment, x: Float, y: Float, volume: Float, paint: WaterPaint, seed: Long) = Arena.ofConfined().use { a ->
        val p = a.pig(paint)
        check(waterDropH.invokeExact(c, x, y, volume, p.names, p.params, p.conc, p.n, seed) as Int, "a drop")
    }

fun waterWash(c: MemorySegment, rings: List<FloatArray>, feather: Float, um: Float, paint: WaterPaint, seed: Long)

    fun waterWash(c: MemorySegment, rings: List<FloatArray>, feather: Float, um: Float, paint: WaterPaint, seed: Long) = Arena.ofConfined().use { a ->
        val (xy, sizes, n) = a.rings(rings)
        val p = a.pig(paint)
        check(waterWashH.invokeExact(c, xy, sizes, n, feather, um, p.names, p.params, p.conc, p.n, seed) as Int, "a wash")
    }

fun waterStats(c: MemorySegment): WaterLedger?

    fun waterStats(c: MemorySegment): WaterLedger? = Arena.ofConfined().use { a ->
        val out = a.allocate(D, 9 + 9 * 8)
        val n = waterStatsH.invokeExact(c, out, 9 + 9 * 8) as Int
        check(n, "reading the water")
        if (n == 0) return@use null
        val v = out.toArray(D)
        val names = (waterSpeciesH.invokeExact(c) as MemorySegment).let { p ->
            nonNull(p, "naming the pigments")
            try { p.reinterpret(Long.MAX_VALUE).getString(0) } finally { freeStringH.invokeExact(p) }
        }.split('\n')
        val pigments = (0 until v[8].toInt()).map { k ->
            val o = 9 + 9 * k
            PigmentLedger(names.getOrElse(k) { "?" }, v[o], v[o + 1], v[o + 2], v[o + 3], v[o + 4], v[o + 5], v[o + 6], v[o + 7], v[o + 8])
        }
        WaterLedger(v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], pigments)
    }

fun waterMap(c: MemorySegment, which: Int, pixels: Int): FloatArray?

    fun waterMap(c: MemorySegment, which: Int, pixels: Int): FloatArray? = Arena.ofConfined().use { a ->
        val out = a.allocate(F, pixels.toLong())
        val n = waterMapH.invokeExact(c, which, out, pixels) as Int
        check(n, "a water map")
        if (n == 0) null else out.toArray(F)
    }

fun aqPigment(name: String): FloatArray?

    fun aqPigment(name: String): FloatArray? = Arena.ofConfined().use { a ->
        val out = a.allocate(F, 8)
        val st = aqPigmentH.invokeExact(a.allocateFrom(name), out) as Int
        check(st, "a pigment")
        if (st == 0) null else out.toArray(F)
    }

fun aqPigmentNames(): List<String>

    fun aqPigmentNames(): List<String> {
        val p = nonNull(aqPigmentNamesH.invokeExact() as MemorySegment, "listing pigments")
        try { return p.reinterpret(Long.MAX_VALUE).getString(0).split('\n') } finally { freeStringH.invokeExact(p) }
    }

fun aqPigmentMake(r: Float, g: Float, b: Float, strength: Float, opacity: Float, density: Float, diameter: Float, stain: Float, lift: Float): FloatArray

    fun aqPigmentMake(r: Float, g: Float, b: Float, strength: Float, opacity: Float, density: Float, diameter: Float, stain: Float, lift: Float): FloatArray =
        Arena.ofConfined().use { a ->
            val out = a.allocate(F, 8)
            check(aqPigmentMakeH.invokeExact(r, g, b, strength, opacity, density, diameter, stain, lift, out) as Int, "making a pigment")
            out.toArray(F)
        }

the segmented brush

fun dbrushNew(c: MemorySegment, kind: Int, width: Float, cfg: FloatArray, hairs: Int, segs: Int, seed: Long): MemorySegment

    fun dbrushNew(c: MemorySegment, kind: Int, width: Float, cfg: FloatArray, hairs: Int, segs: Int, seed: Long): MemorySegment = Arena.ofConfined().use { a ->
        require(cfg.size == 12)
        nonNull(dbrushNewH.invokeExact(c, kind, width, a.allocateFrom(F, *cfg), hairs, segs, seed) as MemorySegment, "making a segmented brush")
    }

fun dbrushFree(b: MemorySegment) { dbrushFreeH.invokeExact(b) }

fun dbrushVary(b: MemorySegment, name: String, values: FloatArray)

    fun dbrushVary(b: MemorySegment, name: String, values: FloatArray) = Arena.ofConfined().use { a ->
        check(dbrushVaryH.invokeExact(b, a.allocateFrom(name), a.allocateFrom(F, *values), values.size) as Int, "varying the brush's $name")
    }

fun dbrushParam(b: MemorySegment, name: String, value: Float)

    fun dbrushParam(b: MemorySegment, name: String, value: Float) = Arena.ofConfined().use { a ->
        check(dbrushParamH.invokeExact(b, a.allocateFrom(name), value) as Int, "setting the brush's $name")
    }

fun dbrushSet(b: MemorySegment, muS: Float, muK: Float, yieldRad: Float, plastic: Float, recover: Float, cohesion: Float)

    fun dbrushSet(b: MemorySegment, muS: Float, muK: Float, yieldRad: Float, plastic: Float, recover: Float, cohesion: Float) =
        check(dbrushSetH.invokeExact(b, muS, muK, yieldRad, plastic, recover, cohesion) as Int, "setting the brush")

fun dbrushInfo(b: MemorySegment): FloatArray

    fun dbrushInfo(b: MemorySegment): FloatArray = Arena.ofConfined().use { a ->
        val out = a.allocate(F, 14)
        check(dbrushInfoH.invokeExact(b, out) as Int, "reading the brush")
        out.toArray(F)
    }

fun dbrushLoad(b: MemorySegment, r: Float, g: Float, bl: Float, hiding: Float, scatter: Float, stiff: Float, drying: Float,

    fun dbrushLoad(b: MemorySegment, r: Float, g: Float, bl: Float, hiding: Float, scatter: Float, stiff: Float, drying: Float,
                   amount: Float, wipe: Float, thinner: Float, turps: Float, oil: Float, side: Float, share: Float, streak: Float, partSeed: Long) =
        check(dbrushLoadH.invokeExact(b, r, g, bl, hiding, scatter, stiff, drying, amount, wipe, thinner, turps, oil, side, share, streak, partSeed) as Int, "loading the brush")

fun dbrushLoadWater(c: MemorySegment, b: MemorySegment, paint: WaterPaint, fill: Float, wipe: Float)

    fun dbrushLoadWater(c: MemorySegment, b: MemorySegment, paint: WaterPaint, fill: Float, wipe: Float) = Arena.ofConfined().use { a ->
        val p = a.pig(paint)
        check(dbrushLoadWaterH.invokeExact(c, b, p.names, p.params, p.conc, p.n, fill, wipe) as Int, "loading the brush with watercolour")
    }

fun dbrushStroke(c: MemorySegment, b: MemorySegment, samples: FloatArray, seed: Long): DoubleArray

samples is x, y, t, pressure, tilt, azimuth, roll per sample (radians).

    fun dbrushStroke(c: MemorySegment, b: MemorySegment, samples: FloatArray, seed: Long): DoubleArray = Arena.ofConfined().use { a ->
        val out = a.allocate(D, 8)
        check(dbrushStrokeH.invokeExact(c, b, a.allocateFrom(F, *samples), samples.size / 7, seed, out) as Int, "a segmented stroke")
        out.toArray(D)
    }

fun dbrushHairs(c: MemorySegment, b: MemorySegment): FloatArray

    fun dbrushHairs(c: MemorySegment, b: MemorySegment): FloatArray = Arena.ofConfined().use { a ->
        val out = a.allocate(A)
        val len = a.allocate(L)
        check(dbrushHairsH.invokeExact(c, b, out, len) as Int, "reading the hairs")
        val n = len.get(L, 0)
        val buf = out.get(A, 0).reinterpret(n * 4)
        try { buf.toArray(F) } finally { freeFloatsH.invokeExact(buf, n) }
    }

fun contactMap(c: MemorySegment, pixels: Int): FloatArray?

    fun contactMap(c: MemorySegment, pixels: Int): FloatArray? = Arena.ofConfined().use { a ->
        val out = a.allocate(F, pixels.toLong())
        val n = contactMapH.invokeExact(c, out, pixels) as Int
        check(n, "the contact map")
        if (n == 0) null else out.toArray(F)
    }

fun dbrushState(b: MemorySegment): ByteArray

    fun dbrushState(b: MemorySegment): ByteArray = bytes("saving the brush") { o, l -> dbrushStateH.invokeExact(b, o, l) as Int }

fun dbrushFromState(bytes: ByteArray): MemorySegment

    fun dbrushFromState(bytes: ByteArray): MemorySegment = fromBytes(bytes, "restoring a segmented brush") { p, n -> dbrushFromStateH.invokeExact(p, n) as MemorySegment }
}