A focused native Android drawing canvas for stylus and USB graphics-tablet input.
Draw with pressure-sensitive brushes, geometric shapes, layers, gestures, and a lightweight programmatic View-based interface.
This repository is a working Android drawing app with a USB input path specifically adapted to the VEIKK VO1060. The codebase already contains the implemented Stage 1 drawing-performance improvements, layer/cache improvements, verified VO1060 report parsing, auxiliary-control parsing, and the one-minute in-app raw-capture diagnostic option.
Important: FEATURES_TO_IMPLEMENT.md is the single source of truth for everything that is still planned, incomplete, deliberately unsupported, or not physically verified. Do not use the old implementation-report/stage-plan files from previous versions; their implemented content has been consolidated here and in this README.
- Freehand touch drawing.
- USB VEIKK VO1060 pen drawing.
- Pressure-sensitive stroke width.
- Pen, Crayon, and Ink brush engines.
- Adjustable brush size, opacity, and density where supported.
- Eraser mode and clear-canvas action (clears the active layer only).
- Undo/redo with a maximum 50-entry undo history.
- Optional floating draggable undo button and two-finger undo gesture.
- Pinch zoom, pan, and two-finger canvas rotation.
- Viewport state is retained by
DrawingViewModelacross Activity recreation/orientation changes. - Hand drawing mode for finger input independent of stylus mode.
- Reset Zoom action to return the canvas to its auto-fit state.
The toolbar is a fixed floating bar at the top of the drawing screen. It contains:
- Main menu (☰): Erase, Hand drawing, Shapes, Clear.
- Brush button: Opens the brush browser.
- Color swatch: Opens the color picker.
- Layers button (▤): Opens the layer dialog.
- Overflow menu (⋮): Save, Settings, Undo, Redo, Reset Zoom.
The toolbar can be hidden (long-press the Clear button to restore it). Toolbar visibility state is persisted across sessions.
- HSV color model with an interactive hue bar and color square.
- Preset color swatches for quick selection.
- Live preview of the selected color.
- Cancelling or backing out of the color picker does not change the active color.
- Brush renderers are isolated under
graphicsdrawing/drawing/brushes/. - Brushes are discovered automatically at startup; the registry does not require a hard-coded brush list.
StrokeSmootheris applied only to brushes that opt into common smoothing; Crayon explicitly opts out because it generates its own particle geometry.- Historical touch samples are fed into the stroke pipeline before the current
MotionEventsample, improving point density without an artificial delay. - A shared
BrushEngineboundary isolates brush lookup, optional common smoothing, and brush rendering. StrokeSmootherworks at render time while raw points remain authoritative for stored strokes, undo/redo, and document coordinates.- Stage 1 performance work is already in the code: Crayon opts out of redundant common smoothing, reusable Crayon rendering buffers are used, per-render
stroke.points.toList()copying was removed, Pen pressure statistics are cached per input sample, and the Ink width buffer is reused. - Stage 2/3 rendering improvements are implemented: Pen/Ink opacity is isolated per stroke to avoid overlap darkening, constant-width paths use smooth cubic interpolation, long Crayon strokes use persistent in-progress rendering caches, Crayon particle sampling avoids per-sample
Randomallocation, and Ink variable-width rendering uses a single filled outline path to remove the fast-stroke chain-link/bead artifact. - Layer-render caches and incremental/composited bitmap paths are implemented in
DrawingEngine/LayerManagerto reduce repeated full-document redraw work.
The current shape tools are:
- Straight line
- Square
- Rectangle
- Circle
- Oval
- Rounded rectangle
Simple geometry recognition is implemented. The Advanced (AI) option is present in Settings as Coming Soon and is not active.
- Multiple layers.
- Active-layer selection.
- Layer visibility, opacity, and clipping state.
- Visual stack is maintained top-to-bottom with recalculated layer numbering.
- Adding a layer inserts it above the current layer.
- Eye toggling updates the layer dialog without closing it.
- Per-layer rendered bitmap caches reduce repeated stroke rendering.
- Full-document/layer previews fit the complete layer content into the thumbnail area.
- Undo/redo includes layer and stroke actions.
Settings are built programmatically and currently contain these sections, in this order:
- Shape Recognition
- Canvas
- Display
- Advanced
The Advanced section is intentionally at the bottom. It contains the development/hardware diagnostic option VEIKK Raw HID Capture.
Current settings include:
Shape Recognition
- Simple Geometry — Uses math to detect lines, circles, rectangles, and triangles. Fast, works offline, no extra download needed.
- Advanced (AI) — Coming Soon. Will use on-device AI for better accuracy on messy or rotated strokes.
Canvas
- Pressure Sensitivity — Varies stroke width based on stylus press force. Default: on.
- Stroke Smoothing — Applies light smoothing to reduce hand tremor. Default: off.
Display
- Show Cursor Crosshair — Shows the custom crosshair at the exact point the stylus draws. Default: on.
- Full Screen Mode — Hides the Android status and navigation bars. Default: off.
- Dark Mode — Uses a dark canvas, menus, and controls with light text and icons. Default: off.
- Floating Undo Button — Shows the draggable undo button on the drawing screen. Default: on.
- Undo Gesture — Undo the latest stroke with a two-finger tap. Default: on.
Advanced
- VEIKK Raw HID Capture — Creates two TXT files in
Downloads/VEIKK: one for VO1060 pen reports, one for raw USB HID reports including tablet-key events. Automatically turns off after 1 minute.
- The Save action exports the composited visible drawing as PNG through Android
MediaStore. - Export location:
Pictures/VEIKK Drawings. - The current app does not reopen editable project/document files.
The current code is based on physical raw captures from the target device, not only on the supplied Windows-driver/reference values.
| Field | Current verified value |
|---|---|
| Pen report ID | 0x02 |
| Report length | 10 bytes |
| X | bytes 2–3, little-endian |
| Y | bytes 4–5, little-endian |
| Pressure | bytes 6–7, little-endian |
| X range | 0..32767 |
| Y range | 0..32767 |
| Pressure range | 0..8191 |
| Hover | 0xC0 |
| Contact/drawing | 0xC1 |
| Pen Button 1 | 0xC2 |
| Pen Button 2 | 0xC4 |
| Out-of-range observed | 0x80 |
| Tilt candidate X | signed byte 9 |
| Tilt candidate Y | signed byte 8 |
The current parser clamps X/Y/pressure to the verified physical ranges and normalizes pressure through the isolated PressureProcessor/PressureCurve path. The pressure curve is currently 1:1.
A separate 8-byte Report ID 0x03 stream is parsed independently from the 10-byte pen report.
| Control | Verified code |
|---|---|
| Tablet Button 1 | 0x0113 |
| Tablet Button 2 | 0x0108 |
| Tablet Button 3 | 0x004B |
| Tablet Button 4 | 0x004E |
| Dial Center | 0x001A |
The diagnostic capture identified the stream on interface 1, endpoint 0x82. These are treated as VEIKK/VO1060-specific control codes; the app does not assume they are generic HID keyboard usages.
The supplied Windows package is reference material only. Windows .sys, .dll, and .exe components are not executed or embedded as Android drivers. The Android implementation uses the Android USB/HID input path and the verified physical report structure.
Secondary reference values such as 13-byte reports, 50800 × 31750 coordinate ranges, and similar Windows-driver assumptions must not override the physical Android captures above.
Implemented and working. The VEIKK VO1060 is mapped to the Android device/app input area used by the drawing View. The tablet pointer is not restricted to the document/canvas content. It works throughout the app's drawing View, and the pointer remains usable while the canvas is zoomed, panned, or rotated.
The current input flow is:
Physical tablet
-> raw VO1060 report
-> verified parser
-> normalize X/Y
-> TabletMapper
-> Android app/drawing View coordinates
-> pointer crosshair / app-space position
-> existing inverse canvas transform
-> document coordinates for drawing
TabletMapper uses the current Android View width/height as the tablet mapping target. The canvas/document transform is applied after the tablet position has been established in View/screen space. This is why changing canvas zoom, pan, or rotation does not move or disable the tablet pointer.
Android portrait/landscape changes also work because the mapping uses the View's current dimensions rather than a hard-coded physical screen resolution.
- Tablet → Android app/View mapping: implemented and working.
- Tablet pointer inside the app: implemented and working.
- Pointer while canvas is zoomed/panned/rotated: working.
- Pointer after Android screen orientation changes: working.
- Tablet → document/canvas coordinates: handled by the existing inverse canvas transform after app/View mapping.
This is not a pending feature. Do not reimplement or replace this mapping architecture unless a future bug or new requirement specifically requires it.
Tilt bytes are already carried through the current input/drawing data structures:
- byte 9 is treated as the X-direction candidate;
- byte 8 is treated as the Y-direction candidate;
- the current parser exposes normalized directional candidates in
-1..1using signed-byte scaling.
However, the following are not confirmed/calibrated yet:
- exact neutral offsets;
- exact physical angle represented by a raw value;
- full physical tilt range;
- correct gain for the advertised 60° range;
- final brush behavior based on tilt.
Tilt-aware brush rendering is therefore still pending and must not be described as fully calibrated functionality.
Settings → Advanced → VEIKK Raw HID Capture starts a maximum one-minute capture session.
When active and the Drawing Activity is foreground with a connected VO1060, the app creates two TXT files under Downloads/VEIKK:
vo1060_raw_YYYYMMDD_HHMMSS.txt— the pen stream.vo1060_usb_diag_YYYYMMDD_HHMMSS.txt— the USB HID diagnostic stream, including auxiliary/tablet-key reports received from the active endpoint.
Safety behavior already implemented:
- Off by default.
- Recording starts only when the drawing Activity is visible and a VEIKK tablet is actually connected/claimed.
- Automatically expires after 60 seconds.
- Leaving the drawing Activity finalizes the files.
- Disconnecting the tablet finalizes the files.
- Capture is buffered rather than forcing a disk write on every USB report.
- Raw capture is independent of the drawing engine and is not required for normal drawing.
- USB debugging is not required after the APK is installed; capture is written to user-accessible storage.
| Path | Responsibility |
|---|---|
graphicsdrawing/MainActivity.kt |
Main screen lifecycle, settings application, drawing view setup, and VO1060 input hookup. |
graphicsdrawing/SettingsActivity.kt |
Programmatic Settings UI and SharedPreferences-backed settings. |
graphicsdrawing/drawing/DrawingEngine.kt |
Stroke lifecycle, rendering, shape drawing, layer composition, cache handling, bitmap generation, clear, undo, and redo. |
graphicsdrawing/drawing/BrushSettings.kt |
Active brush/tool state. |
graphicsdrawing/drawing/StrokeSmoother.kt |
Common render-time point smoothing for brushes that opt in. |
graphicsdrawing/drawing/brushes/ |
Pen, Ink, Crayon renderers plus brush interfaces/definitions/registry. |
graphicsdrawing/model/DrawingModels.kt |
Drawing points, strokes, and layers. |
graphicsdrawing/model/DrawingViewModel.kt |
Lifecycle-retained drawing state, engine, mapper, document dimensions, and viewport state. |
graphicsdrawing/layers/LayerManager.kt |
Layer storage, visibility/opacity/clipping state, and 50-entry undo/redo history. |
graphicsdrawing/input/VeikkUsbInput.kt |
USB discovery, permission, connection lifecycle, packet reading, pen parsing dispatch, auxiliary report dispatch, and raw-capture hooks. |
graphicsdrawing/input/VeikkVO1060ReportParser.kt |
Verified 10-byte VO1060 pen report parser. |
graphicsdrawing/input/VeikkAuxiliaryEvent.kt |
Separate parser/model for Report ID 0x03 auxiliary controls. |
graphicsdrawing/input/TabletSample.kt |
Parsed tablet sample including raw values, normalized pressure, tilt candidates, and verified pen button/contact state. |
graphicsdrawing/input/TabletMapper.kt |
Maps raw VO1060 coordinates to Android app/View coordinates using current View width/height. |
graphicsdrawing/input/StrokeInput.kt |
Normalized sample passed into the drawing/stroke pipeline. |
graphicsdrawing/input/VeikkCaptureController.kt |
One-minute user-controlled raw-capture lifecycle and file-session coordination. |
graphicsdrawing/input/VeikkRawCapture.kt |
User-accessible VO1060 pen report file output. |
graphicsdrawing/input/VeikkUsbDiagnosticCapture.kt |
User-accessible USB HID diagnostic file output. |
graphicsdrawing/input/VeikkRawReportLogger.kt |
Parsed/raw logging support used by the diagnostic path. |
graphicsdrawing/input/VeikkDebugConfig.kt |
Debug configuration for tablet input. |
graphicsdrawing/input/MultiFingerGestureDetector.kt |
Two-finger zoom/pan/rotate and optional undo gesture detection. |
graphicsdrawing/pressure/ |
Pressure normalization and isolated pressure curve abstraction. |
graphicsdrawing/ui/TabletDrawingView.kt |
Drawing surface, touch input, tablet input, viewport transform, pointer display, and drawing/layer controls. |
graphicsdrawing/ui/ToolbarController.kt |
Toolbar actions, settings/persistence wiring, dialogs, floating undo bubble, and export actions. |
graphicsdrawing/ui/TopBar.kt |
Fixed floating toolbar with main menu, overflow menu, brush/color/layer buttons, and dismissible panel menus. |
graphicsdrawing/ui/ToolDialogs.kt |
Shape and layer dialogs plus previews/icons. |
graphicsdrawing/ui/BrushDialog.kt |
Brush browser, previews, and per-brush option controls. |
graphicsdrawing/ui/ColorPickerDialog.kt |
Custom HSV color picker with hue bar, color square, and preset swatches. |
graphicsdrawing/ui/ShapeType.kt |
Available shape enum (Off, Line, Square, Rectangle, Circle, Oval, Rounded Rectangle). |
graphicsdrawing/storage/DrawingSaver.kt |
PNG export through Android MediaStore. |
app/src/main/AndroidManifest.xml |
Android application manifest. |
app/build.gradle.kts |
Android/Kotlin configuration, SDK levels, Java 17, dependencies, and sibling Kotlin source-set configuration. |
samples/ |
Sample .adbrush preset files (e.g., Ink_Pen_Texturized.adbrush). |
The project intentionally keeps the Kotlin source under the sibling graphicsdrawing/ directory and adds it to the app module through the Gradle sourceSets configuration.
- Android Studio with a Java 17-compatible environment.
- Android SDK Platform 35.
- Android 8.0 / API 26 or newer.
- USB-OTG support and a compatible VEIKK VO1060 for tablet testing.
- Touch drawing works without a graphics tablet.
From the project root:
./gradlew assembleDebugWindows PowerShell:
.\gradlew.bat assembleDebugThe debug APK is generated under app/build/outputs/apk/debug/.
For USB-specific changes, install the APK on a physical Android device and test the VO1060 through USB-OTG. Raw capture itself does not require a USB-debugging connection.
Only these project Markdown files are intended to remain as maintained project documentation:
README.md— current implemented behavior, current architecture, verified hardware findings, build/use information, and documentation index.FEATURES_TO_IMPLEMENT.md— the single source of truth for planned, incomplete, deliberately deferred, or not-yet-verified features. This replaces the older collection of separate implementation plans/reports.CONTRIBUTING.md— contribution, testing, and review workflow.
The .adbrush format is documented below instead of using a separate ADBRUSH_FORMAT.md file.
See FEATURES_TO_IMPLEMENT.md. It is intentionally organized by priority and by verification status so future coding work does not require reading multiple historical Markdown files.
Highest-priority open items currently include:
- Verify/calibrate tilt neutral offsets and physical-angle scaling before changing brush behavior.
- Capture and decode the dial clockwise/anticlockwise packets; keep dial rotation disabled until verified.
- Add a configurable pressure-response curve and, where useful, separate pressure/tilt temporal smoothing without introducing latency.
- Implement Advanced (AI) shape recognition only when the desired offline/on-device model behavior is defined and tested.
- Continue the longer-term brush/stroke architecture improvements without breaking layers, undo/redo, viewport transforms, touch input, or existing performance work.
- Add editable project/document save/load, additional tools/brushes, adaptive layouts/accessibility, and automated tests as separately tracked future work.
The app-owned brush preset format is a UTF-8 JSON file with the .adbrush extension. It stores preset metadata/defaults only; rendering remains in the app's native brush engine.
Current format:
{
"format": "adbrush",
"version": 1,
"id": "textured-ink-pen",
"name": "Textured Ink Pen",
"engine": "PEN",
"defaults": {
"size": 8,
"opacity": 85,
"density": 100
},
"options": ["size", "opacity"]
}engine must currently refer to one of the app's built-in brush engine/type values (PEN, INK, CRAYON). The preset format is intentionally separate from the drawing model, so importing/removing a preset does not rewrite the built-in brush implementations.
A sample preset (Ink_Pen_Texturized.adbrush) is included under samples/. It also carries an optional source block documenting its origin from a Krita brush preset; that block is informational and is not used by the rendering engine.
Do not perform a large rewrite merely to add one new tablet or drawing feature.
Before changing code:
- Understand the current responsibility of the file.
- Identify the exact reason the change is needed.
- Make the smallest isolated change possible.
- Build/test.
- Run regression tests for existing drawing, layers, undo/redo, viewport, touch input, and USB input where applicable.
- Move to the next stage only after the previous stage is stable.
Unverified hardware behavior must never be presented as confirmed functionality.
This project is licensed under the terms included in LICENSE.