About ink-custom-brush-builder
A skill for implementing programmatic custom brushes with the Android Ink API. It guides coding agents through brush structure, input-driven behavior, textures, and serialization using the public Kotlin APIs available from `1.1.0-alpha03`.
Key features
- 01BrushFamily and multi-coat structure
- 02Stylus and touch behavior node trees
- 03Tip geometry and paint configuration
- 04Texture layers and bitmap store guidance
- 05Brush encoding and decoding
Use cases
- 01Building a pressure-sensitive custom brush
- 02Creating calligraphy or textured brush designs
- 03Mapping stylus tilt or speed to brush behavior
- 04Persisting custom brush definitions
Install
Copy the command and run it in a terminal.
$
npx skills add https:/ / github.com/ android/ skills --skill ink-custom-brush-builderAfter installing, type/ink-custom-brush-builderin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/brush-behaviors.mdbrush-structure.mdexamples.mdtextures-and-serialization.mdreferences/android/develop/ui/compose/touch-input/stylus-input/ink-api-brush-apis.mdDetails
From the project READMECustom brushes in the Android Ink API are built around the BrushFamily
hierarchy:
BrushFamily
├── InputModel (BrushFamily.InputModel.SlidingWindowModel or PASSTHROUGH_MODEL)
└── BrushCoat[] (one or more rendering layers)
├── BrushTip (shape: scaleX, scaleY, cornerRounding, rotationDegrees, slantDegrees, pinch)
│ └── BrushBehavior[] (dynamic response to stylus or touch input)
│ └── Tree: TargetNode(input = [Operators](input = SourceNode))
└── BrushPaint (visual appearance)
├── SelfOverlap (ANY, ACCUMULATE, or DISCARD)
├── TextureLayer[] (BrushPaint.TilingTexture or BrushPaint.StampingTexture)
└── BrushPaint.ColorFunction[] (OpacityMultiplier or ReplaceColor)
Create a Brush by combining a custom BrushFamily with a Compose Color,
size, and epsilon:
val brush = Brush.createWithComposeColor(
family = myCustomFamily,
color = Color.Black,
size = 10f,
epsilon = 0.1f,
)
Version and experimental API requirements :
- Requires
androidx.ink1.1.0-alpha03and higher : In1.0.0(Stable) and1.1.0-alpha01-alpha02,BrushFamily(...),BrushCoat,BrushTip,BrushPaint,BrushBehavior, andandroidx.ink.brush.behavior.*nodes were@RestrictTo(LIBRARY_GROUP)(in1.0.0stable, custom brushes can only be loaded from binary proto streams usingBrushFamily.decode). Starting in1.1.0-alpha03and higher , programmatic custom brush constructors graduated to public API (BrushPaint.TilingTexture,BrushPaint.StampingTexture,BrushPaint.ColorFunction,BrushFamily.InputModel.SlidingWindowModel, and all behavior nodes).- No
@OptInrequired in1.1.0-alpha03and higher : All public custom brush classes andink-storageBrushFamilyserialization APIs (BrushFamily.encodeanddecode,AndroidBrushFamilySerialization,BrushFamilyDecodeCallback) are unannotated in1.1.0-alpha03and higher (ExperimentalInkCustomBrushApiis@RestrictTo(LIBRARY_GROUP)in1.1.0-alpha05and higher, and is only required onBrushFamilyserialization in1.0.0stable).
Workflow and task routing
Follow these conditional instructions based on the requested custom brush task:
- Step 1 (Dependencies) : When setting up an Android project for programmatic custom brushes, ask user permission before editing build files and verify that
androidx.ink:ink-brush,androidx.ink:ink-brush-compose,androidx.ink:ink-storage, andandroidx.ink:ink-nativeloaderuse version1.1.0-alpha03or higher ingradle/libs.versions.tomlandapp/build.gradle.kts. - Step 2 (Hierarchy, tip shape, paint, and input smoothing) : When defining
BrushFamilyand multi-coatBrushCoatlayers, configuringBrushTipgeometry (scaleX,scaleY,cornerRounding,rotationDegrees,slantDegrees,pinch), settingBrushPaintproperties (SelfOverlap,ColorFunction), or smoothing stroke input withBrushFamily.InputModel.SlidingWindowModel, you MUST follow brush-structure.md. For baselineBrushcreation or1.0.0resource loading, review Brush APIs. - Step 3 (Dynamic stylus and touch behaviors) : When making a brush respond dynamically to stylus pressure, tilt, speed, direction, or procedural noise using
BrushBehaviornode trees (SourceNode,DampingNode,NoiseNode,ResponseNode,TargetNode,PolarTargetNode), you MUST follow brush-behaviors.md. - Step 4 (Textures and serialization) : When adding
BrushPaint.TilingTextureorBrushPaint.StampingTexturelayers, implementingTextureBitmapStore, or encoding and decoding customBrushFamilydefinitions (BrushFamily.encodeanddecode,AndroidBrushFamilySerialization,BrushFamilyDecodeCallback), you MUST follow textures-and-serialization.md. Inform the user which texture image files (such asapp/src/main/res/drawable-nodpi/pencil_grain.png) to add to their project and register inTextureBitmapStore. - Step 5 (Complete brush recipes) : When building a complete pressure pen, calligraphy nib, shading pencil, watercolor wash, or multi-coat shadow brush from scratch, start from the copy-ready implementations in examples.md.
- Compose drawing surface, eraser, or persistence : For Compose drawing surface setup,
InProgressStrokeswet and dry rendering, geometry-based erasing, or Room stroke persistence, suggest theink-app-builderskill.
Antipatterns
- NEVER use raw
ink.proto.*builders to construct brushes---always use the public Kotlin constructors (BrushFamily,BrushCoat,BrushTip,BrushPaint,BrushBehaviorfromandroidx.ink.brush.*). - NEVER manually scale stroke width in a touch listener for pressure or tilt sensitivity---use
BrushBehaviormappingSourceNode.Source.NORMALIZED_PRESSUREorTILT_IN_RADIANStoTargetNode.Target.SIZE_MULTIPLIERorWIDTH_MULTIPLIER. - NEVER use manual
Canvasbitmap stamping orBitmapShaderfor textured brushes---useBrushPaint.TilingTextureorBrushPaint.StampingTexturewith aTextureBitmapStore. - NEVER use
BlendMode.SRC_OVERon the final (or only)TextureLayer---useBlendMode.MODULATE,DST_IN,DST_OUT,SRC_ATOP, orSRC_INso edge anti-aliasing andOPACITY_MULTIPLIERbehaviors aren't overridden. - NEVER serialize
BrushFamilyas JSON, Gson, Moshi, orParcelable---always useBrushFamily.encodeandBrushFamily.decodeorAndroidBrushFamilySerializationfromandroidx.ink.storage. - NEVER implement manual Bézier (
quadTo,cubicTo) smoothing ---configureBrushFamily.InputModel.SlidingWindowModelfor input smoothing andDampingNodefor behavior smoothing. - NEVER use
Random.nextFloatin draw loops for organic stroke wobble---attach aNoiseNodevaried over stroke distance inBrushBehavior.
Comments
Markdown supported