Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active November 18, 2022 05:37
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nitaku/8377916 to your computer and use it in GitHub Desktop.
Save nitaku/8377916 to your computer and use it in GitHub Desktop.
Tablet test (desktop)

And now for something completely different: a Windows desktop application! Open this gist for the code.

This example uses a python wrapper around Wacom's Wintab/Feel driver to read a Wacom stylus input, and draws a representation of it onto a Tkinter canvas. Each circle represents a point, and its radius is proportional to the pressure of the stylus.

To run the application, you will need: a (Wacom?) tablet or a tablet PC, Microsoft Windows (I tried it with 8.1), Python 3.3, cgkit 2, the code from this repo. To actually run it, open a command prompt where you put the python files, then execute:

python test.py

A stylus input is really useful for graphic applications, because it features subpixel resolution (more precise than screen pixels) and pressure data.

Based on the code and suggestions found in this blog post. Adding a custom function to the Tkinter main loop is documented here, drawing a circle onto the canvas here.

from cgkit import wintab
from cgkit.wintab.constants import *
class Point:
''' A class that respresents stylus data. '''
def __init__(self, packet, stylus):
''' Initialize a new Point given a Wintab packet and the stylus originating it. '''
self.t = packet.time
self.x = packet.x * float(stylus.window.winfo_screenwidth()) / float(stylus.context.inextx) - stylus.window.winfo_rootx()
self.y = stylus.window.winfo_screenheight() - packet.y * float(stylus.window.winfo_screenheight()) / float(stylus.context.inexty) - stylus.window.winfo_rooty() # Y coordinate is flipped
self.p = packet.normalpressure/float(stylus.max_pressure)
self.button = packet.buttons
self.cursor = {1: 'pen', 2: 'eraser'}[packet.cursor]
def __repr__(self):
s = '<t:%d x:%f y:%f p:%f %s' % (self.t, self.x, self.y, self.p, self.cursor)
if self.button != 0:
s += ' b:%d' % self.button
s += '>'
return s
class Stylus:
def __init__(self, window, callback, max_pressure=1024, update_ms=1):
''' Create a new stylus for the given Tk window. 'callback' is the function to be called for each new point. '''
self.window = window
self.callback = callback
self.max_pressure = max_pressure
self.update_ms = update_ms
self.context = wintab.Context()
self.context.pktdata = (PK_TIME | PK_X | PK_Y | PK_NORMAL_PRESSURE | PK_BUTTONS | PK_CURSOR)
self.context.open(self.window.winfo_id(), True)
# schedule an update of the Stylus
self.window.after(self.update_ms, self.update)
def update(self):
''' Get new points from the buffer and invoke the callback for each one. '''
packetExtents = self.context.queuePacketsEx()
if packetExtents[0] is not None and packetExtents[1] is not None:
packets = self.context.packetsGet(packetExtents[1]-packetExtents[0])
for p in packets:
self.callback(Point(p, self))
# reschedule the update of the Stylus
self.window.after(self.update_ms, self.update)
from tkinter import *
from tablet import Stylus
# create a window with a canvas to draw onto
window = Tk()
window.title('Tablet test')
canvas = Canvas(window)
# define a callback for drawing points
def draw(p):
''' Draw a point as a circle with a radius proportional to the pressure. '''
if p.button == 1:
x, y, r = p.x, p.y, p.p*10
canvas.create_oval(x-r, y-r, x+r, y+r, outline='black', width=1)
canvas.pack(fill=BOTH, expand=1)
# create the stylus object, binding the callback
stylus = Stylus(window, callback=draw)
# start the application
window.mainloop()
@JoergS5
Copy link

JoergS5 commented Oct 7, 2018

Thanks a lot for your code. I never used Python, and after half an hour have good results seeing the movement of my wacom tablet!
I even played around by zooming in into point details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment