Repository navigation
Installers that need nothing else, and a demo worth looking at - #3
Merged
Merged
Conversation
The old sample begged the question: a black-on-white cat is already line art, so tracing it proves nothing about what happens to an ordinary picture. examples/castle.png is a colour landscape - towers, crenellations, bridge arches, a treeline, mountains - and the demo now shows it beside the phone drawing it, so the colour-in / strokes-out step is the first thing a visitor sees. The phone in the mock-up lies on its side because the scene is landscape; on a portrait screen a wide picture sits in a thin band and reads as underwhelming rather than accurate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Downloading a repository, installing Python, learning what a virtualenv is and separately fetching Android platform-tools is a lot to ask of someone who wants to draw a picture on their phone. The packaged builds now carry Python, OpenCV, Tk and adb inside them: a Windows .msi that installs like any other program, and a macOS .dmg for both architectures. find_adb learned to look inside the bundle, ahead of PATH, so an installed AutoDraw behaves the same on every machine rather than inheriting whichever adb happens to be lying around. In a source checkout the bundled list is empty and nothing changes. The build is one command - tools/build_app.py, used identically by hand and by CI - and it ends by running the packaged app's own self-test: import the whole application, resolve adb, run it. That check exists because the first build looked perfect and died on launch with 'No module named adbtouch.vectorize': the vectoriser is imported lazily through __getattr__ to keep the core library dependency-free, and PyInstaller cannot see through that. A build that ships incomplete now fails here instead. WiX is pinned to 5: version 6 refuses to build until someone accepts a separate maintenance-fee licence, which a build script cannot do. Nothing is code-signed. The release notes say which button to press. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
*.spec was there to ignore the spec PyInstaller generates in the repository root, but it matched packaging/AutoDraw.spec too - so 'git add -A' skipped it without a word and every packaging job failed on a checkout that did not contain the file it was told to build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
macos-13 has been retired. A job asking for a retired label does not fail: it queues indefinitely, and everything downstream waits behind it - the release job included. macos-15-intel is the current Intel label, and every packaging job now has a timeout so a runner that never arrives fails loudly within half an hour instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nvas Two gaps the packaging left. Installed builds carry their own adb, but a source checkout still expected you to find platform-tools yourself - so run.bat and run.sh now fetch it when the machine has none, find_adb looks in a platform-tools directory beside the working directory, and the whole lookup order is written down in the README. The second gap was having nothing to test against. tools/test_canvas.py serves a drawing canvas over adb reverse and opens it on the device: no app to install, and it works the same on a phone over USB, a phone over wireless ADB, and an emulator. The page counts strokes and points as they arrive and draws a labelled grid, and --pattern draws a rectangle inset by a tenth of the screen with its diagonals and a centre cross. Held against the grid that answers the question this project keeps asking of unfamiliar hardware: are the coordinates landing where they were aimed, or is the digitizer scaled, swapped or mirrored? Device.adb() came out of it - the public way to run reverse, forward, push and install without callers assembling -s <serial> themselves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two things this could not do on the phone it was tested against, a Pixel 8 Pro on Android 17. It could not draw at all. sendevent fails there with Permission denied on every line - not a file mode problem, since the shell user is in the input group and the node is rw for it, but SELinux denying the shell domain the write. The script still exits cleanly, so the app reported "Drawing 414 strokes..." and did nothing, which is the worst way for this to fail. There is now a probe, supports_raw_touch, and an automatic fallback through input motionevent: separate invocations join into one gesture, so a polyline still arrives as a single stroke. adbtouch info says which path a device will take, and the app says so too, with an estimate - that path costs about 110ms per point, measured, and a progress bar creeping along with no explanation is indistinguishable from one that has hung. And it drew like a machine. adbtouch/hand.py rounds corners over a capped radius, varies the pen speed by varying point spacing (every point costs the same to send, so spacing is speed), adds a slow tremor rather than per-point static, overshoots stroke ends and starts them a fraction late, and reorders strokes nearest-first the way someone would rather than in contour order. One amount dial scales all of it; the app exposes it, and a speed slider, next to the existing controls. Verified against the phone: the calibration pattern lands exactly where it was aimed, 8 strokes and 970 points, with visible wobble. tools/test_canvas.py is what made that checkable - a drawing canvas served over adb reverse and opened on the device, no app to install. Its page paints its own white background, because a browser with dark mode for web pages turns a transparent canvas black, and black ink on black looks exactly like touches that never arrived. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MAXAWER
force-pushed
the
feature/installers-and-demo
branch
from
August 26, 2026 16:08
7c43dc4 to
ff55202
Compare
The input command costs a whole process per point - 110ms measured on a Pixel 8 Pro - and that is not pacing that can be tuned away. A sixty-point stroke takes seven seconds, and nothing that takes seven seconds looks hand-drawn. The ceiling is the transport, so the transport had to go. injector/ is a 2.5 KB jar, built by tools/build_injector.py from one Java file and shipped inside the package. It is started once through app_process under the shell uid, where hidden-API restrictions do not apply, and reads points from stdin: D/M/U to draw, S to wait. Injection then costs microseconds and the wait between points is ours to set, which is the whole point - timing is what a hand is. Device.draw_paths grew a method argument; auto takes raw events where SELinux allows them, the injector where it does not, and input if even that fails. Two things had to be learned the hard way. Events sharing a millisecond get coalesced, so the injector forces event time forward. And instant is not zero: whatever receives the touches samples them per frame, so a stroke delivered in two milliseconds arrives as a press and a release with nothing between - the first attempt drew 475 of 1542 points and looked like confetti. Six milliseconds is a frame at 120Hz and is as fast as anything can actually see. adbtouch/paths.py joins what findContours fragments back into strokes and drops specks. That is the one change that helps speed and quality together: 215 strokes became 136 on the sample image, each saved fragment being a pen lift that costs time and shows. Measured end to end against the phone, drawing the castle on a canvas served by tools/test_canvas.py: 136 strokes, all 1,679 points registered, about 19 seconds. The same picture through input would have been 3 minutes 40. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two things a first-time visitor decides on: does this do something impressive,
and can I run it without a toolchain. Neither had a good answer.
A demo that proves something
The old sample was a black-on-white cat — already line art, so tracing it showed
nothing about what happens to an ordinary picture.
examples/castle.pngis acolour landscape with towers, crenellations, bridge arches, a treeline and
mountains, drawn from scratch by
tools/make_sample.pyso the repository ownsits demo art with no licence to track.
docs/demo.gifnow shows the colour source beside the phone drawing it — 215strokes, 1,542 touch points, the real path list in the real order. The phone in
the mock-up lies on its side because the scene is landscape; on a portrait screen
a wide picture sits in a thin band and reads as underwhelming rather than
accurate.
Installers that carry everything
Downloading a repository, installing Python, learning what a virtualenv is and
separately fetching Android platform-tools is a lot to ask of someone who wants
to draw a picture on their phone.
AutoDraw-1.1.0-x64.msi, WiX 5. Program Files, Start Menu entry,uninstaller, ARP icon.
.dmgwith the drag-to-Applications layout, built for AppleSilicon and Intel separately.
All of them carry Python, OpenCV, Tk and adb inside.
find_adblearned tolook inside the bundle, ahead of
PATH, so an installed AutoDraw behaves thesame on every machine; in a source checkout
bundled_candidates()returns anempty list and nothing changes.
One build command,
tools/build_app.py, used identically by hand and by CI.The self-test, and why it exists
The first Windows build looked perfect and died on launch:
The vectoriser is imported lazily through
__getattr__so the core library staysdependency-free, and PyInstaller's static analysis cannot see through that. I had
"verified" the build by checking the process was still alive after eight seconds
— it was, because an error dialog is a process.
So every packaged build now ends by running
AutoDraw --selftest: import thewhole application, resolve adb, run it, report. An incomplete bundle fails the
build instead of failing on a desktop.
Verified
On this Windows machine:
python tools/build_app.py --msi→ self-test passes, adb resolves to thebundled copy and reports
1.0.41.msiexec /a(no system change) and the extractedpayload passes the same self-test from its install location.
The macOS half has no local equivalent — this PR's checks are the first real run
of it. The build jobs deliberately also fire on pull requests touching
packaging/, because packaging breaks quietly and only on the platform you arenot using.
Two things to know before merging
Android SDK licence, which restricts redistribution. scrcpy and similar tools
bundle it anyway;
NOTICE.txtships beside the binary, andtools/build_app.py --no-adbbuilds without it if you would rather not.right-click → Open on first launch. The release notes say so, with the
xattrcommand. Signing needs an Authenticode certificate and an AppleDeveloper account.
WiX is pinned to 5.0.2: version 6 and later refuse to build until someone accepts
a separate maintenance-fee licence, which a build script cannot do unattended.