Introduction: DIY Drawing Tablet ✏️

About: An Electronics engineer and a hobbyist. I love to keep experimenting with microcontrollers.

Hello maker !

Few weeks ago I wanted to draw a digital art on my computer. I tried using my computer's mouse but that did not give good results and the cost of commercial drawing tablets is so high that buying them for a single drawing is not really worth it.

So, I decided to make my own drawing tablet. I made a simple pen that transmits IR rays when a push button is pressed. And believe me that was all for the circuit part. The software part of this project was to track that pen and make it draw on a canvas on my screen. And to do that I had to write a program that tracks the position of my pen and uses that information to draw on a canvas.

I also ended up adding a couple of features to the software like the eraser, pen/background color selection, undo option, bucket fill tool and most importantly a cursor option to just move around the canvas without drawing over it.

In this instructable, I will be guiding you on how you can make your own drawing tablet pen and use my software in your computer. So, if you are interested then follow the next few steps and draw your first digital master piece with your own DIY Drawing Tablet.

Supplies

  1. Computer
  2. Webcam
  3. Veroboard
  4. IR Led
  5. Resistor (220 ohms)
  6. Pushbutton
  7. Single cell battery(if you want to make it wireless or else you can just use a USB cable to power the led)
  8. Battery charging/protection circuit
  9. Soldering iron to connect the components
  10. Glue gun (optional)

Step 1: Making the Pen

The circuit to make the pen is quite simple. Just connect an IR led in series with a resistor and a push button. Do follow the circuit shown above. If you do not have a battery you can just use a USB cable to power the led.

One thing to note when soldering the components, is the placement of the IR led. Make sure it is pointed upward and does not get blocked by your hand when you hold the pen in writing position. Even better, you can use a 18650 cell and design a cylindrical cover around it with all the components enclosed inside the casing. This would make it look a lot better and also improve the grip of the pen.

So, I will leave it up to you to figure out a way to solder the pcb and wait for you in the next step :)

Step 2: Installing the Software

Download the software from the link given below.

LINK

Open the .exe file from the main folder.

After this you will see a screen similar to the one in the first image.

Now select four points on the screen according to the label on the screen. These points act as the ROI for your drawing pad. Basically it will determine how large area of your desk you can use to draw on using the IR pen.

Once you select the four points, you will be greeted with three screens. The first one will be a console window which will display the status of the software. The second one will be our main drawing canvas and the last one is the control panel from where you can control the color of the pen tool and the background. Also you can increase/decrease the size of the pen/eraser tool. And also there is a toggle to select the pen/eraser/fill tool. In the bottom of the control panel you can view the selected background and pen color. There is also an option to see the original camera feed if you want. (Refer image 2)

The trackbar control menu is not the only way to select the tools. I have assigned shortcuts for few frequently used tools.

Below are the shortcuts -

1. Brush - b

2. Eraser - e

3. Bucket Fill - f

4. Undo - z

5. Enable/Disable Cursor - k

6. Exit - ESC

Now simply start drawing with the pen in the defined region and have fun.

Step 3: Using the Software With Your Phone's Camera

If you don't have a webcam, you can still use this software with your smartphone as an IP camera.

But for that you will have to make some changes in the data file of the software. So, just go ahead and open the data.txt file from the main folder. Once you open the file you will see two lines (Refer 2nd image). The first line is basically the index of your camera and that's what you will have to change. Also if you are using multiple webcams you can select a particular camera by entering it's index here.

But before that open the IP webcam app on your phone and go to the video preferences. Note that you have to select the video resolution as 640x480 (Refer 3rd image). Now start the server.

In the bottom you see a url which will help you access the video feed of the camera.

Now just type the url in the data text file in place of "0". And in the end of the url add a "/video" to get the feed.

Eg. http://192.168.0.103:8080/video

After this just save the data file and you are good to go.

The second line in the data file represents the color of the cursor. By default it is set to grey (127,127,127). You can change it if you want.

Step 4: Sauce Code

By no means I claim that this software is a perfect replacement for drawing tablets. I mean, I named it Wack-om for a reason. So, if you want to improve it or maybe add more features to it then I'm providing the source code below. Feel free to modify it. Do note that my coding skills are worse than 13 year old's out there so, go through my code at your own risk.

import cv2
import numpy as np
from time import sleep
import webbrowser

with open('data.txt', 'r') as file:
    data = []
    for lines in file:
            data.append(lines.rstrip('\n'))
    camera_var = data[0]
    if camera_var.isnumeric():
        camera_var = int(camera_var)
    data[1] = data[1].split(",")
    num_list = []
    for d in data[1]:
        num_list.append(int(d)) 
    cursor_color = tuple(num_list)

    

cap = cv2.VideoCapture(camera_var)
fw, fh = 1080, 720
w, h = 640, 480

doonce = 0
pointsList = []
prevX, prevY = 0, 0
old_bg = (0,0,0)
canvas = np.zeros((fh,fw,3),np.uint8)
canvas_copy = np.zeros((fh,fw,3),np.uint8)
canvas[:] = (0,0,0)
currX, currY = 0, 0
control_panel = np.zeros((100,600,3), np.uint8)
# Tuple to store undo redo x,y and t
x_y_t = []
global undo_redo_index
undo_redo_index = 0
bucket_selected = False
print("Print ESC Key To Close")
#Disable Tool
disable_tools = False
counter = 0
info_var = -1

# Status Variables
tool_ed, tool_sel, tool_color, bgr_color, tool_size, display_ed, undo_option = "No", "Brush", "(0,0,255)","(0,0,0)", "1", "Disable", "Not Available"  


def getPoints(event, x, y, flags, param):
    global pointsList, doonce
    if event == cv2.EVENT_LBUTTONDOWN:
        if x>=414 and x<=637 and y>=345 and y<=390:
            webbrowser.open('https://www.youtube.com/channel/UCEYapi2zJnVw1A7xAmqBzmw?sub_confirmation=1', new=2)
            if (len(pointsList)!=0):
                pointsList.pop()
        pointsList.append([x,y])
        if (len(pointsList)>3):
            print("Warp Points : ",pointsList)
            print("--------------------------------------------------------")
            print("Keyboard Shortcuts : Brush-b, Eraser-e, Bucket Fill-f")
            print("Undo-z, Disable/Enable Tools-k, Close-ESC, Info-i")
            print("--------------------------------------------------------")
            create_Trackbars()
            doonce = 1


def empty(a):
    pass



def create_Trackbars():
    cv2.namedWindow("Control Panel")
    cv2.resizeWindow("Control Panel",600,640)
    cv2.createTrackbar("Main Feed","Control Panel",0,1,empty)
    cv2.createTrackbar("Pen Red","Control Panel",255,255,empty)
    cv2.createTrackbar("Pen Green","Control Panel",0,255,empty)
    cv2.createTrackbar("Pen Blue","Control Panel",0,255,empty)
    cv2.createTrackbar("Pen/E Size","Control Panel",1,50,empty)
    cv2.createTrackbar("Bg Red","Control Panel",0,255,empty)
    cv2.createTrackbar("Bg Green","Control Panel",0,255,empty)
    cv2.createTrackbar("Bg Blue","Control Panel",0,255,empty)
    cv2.createTrackbar("B / E / F","Control Panel",0,2,empty)



def GetStatus():
    global tool_ed, tool_sel, tool_color, bgr_color, tool_size, display_ed, undo_option
    print("--------------------------------------------------------")
    print("Keyboard Shortcuts : Brush-b, Eraser-e, Bucket Fill-f")
    print("Undo-z, Disable/Enable Tools-k, Close-ESC, Info-i")
    print("--------------------------------------------------------")
    print("Tools Disabled : "+tool_ed)
    print("Tool Selected : "+tool_sel)
    print("Tool Color : "+tool_color)
    print("Background Color : "+bgr_color)
    print("Tool Size : "+tool_size)
    print("Main Feed Enabled/Disable : "+display_ed)
    print("Undo Option : "+undo_option)



def warpImage(frame):
    frame = cv2.resize(frame, (fw,fh))
    pts1 = np.float32([pointsList[3], pointsList[2], pointsList[1], pointsList[0]])
    pts2 = np.float32([[0,0], [w,0], [0,h], [w,h]])
    matrix = cv2.getPerspectiveTransform(pts1, pts2)
    output = cv2.warpPerspective(frame, matrix, (w,h))
    return output


if doonce == 0:
    global imgr
    ret, img = cap.read()
    imgr = cv2.resize(img, (fw,fh))
    cv2.putText(imgr, "Point 1",(5,30),cv2.FONT_HERSHEY_SIMPLEX,1,(255,0,0),3)
    cv2.putText(imgr, "Point 2",(954,30),cv2.FONT_HERSHEY_SIMPLEX,1,(255,0,0),3)
    cv2.putText(imgr, "Point 3",(5,705),cv2.FONT_HERSHEY_SIMPLEX,1,(255,0,0),3)
    cv2.putText(imgr, "Point 4",(954,705),cv2.FONT_HERSHEY_SIMPLEX,1,(255,0,0),3)
    cv2.putText(imgr, "Please Consider Subscribing - Engineer Kid",(180,330),cv2.FONT_HERSHEY_SIMPLEX,1,(0,255,0),2)
    cv2.putText(imgr, "CLICK HERE !",(420,377),cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),3)
    cv2.rectangle(imgr, (414,345), (637,390), (0,0,255), 3)
    cv2.imshow("Frame",imgr)
    cv2.setMouseCallback('Frame', getPoints)
    


while True:        
    if doonce == 1:
        cv2.destroyWindow("Frame")
        disp_mainfeed = cv2.getTrackbarPos("Main Feed","Control Panel")
        red_value = cv2.getTrackbarPos("Pen Red","Control Panel")
        green_value = cv2.getTrackbarPos("Pen Green","Control Panel")
        blue_value = cv2.getTrackbarPos("Pen Blue","Control Panel")
        pen_color = (blue_value,green_value,red_value)
        pen_thickness = cv2.getTrackbarPos("Pen/E Size", "Control Panel")
        bg_red = cv2.getTrackbarPos("Bg Red", "Control Panel")
        bg_green = cv2.getTrackbarPos("Bg Green", "Control Panel")
        bg_blue = cv2.getTrackbarPos("Bg Blue", "Control Panel")
        tool_select = cv2.getTrackbarPos("B / E / F", "Control Panel")  
        if old_bg != (bg_blue,bg_green,bg_red):
            canvas[:] = (bg_blue,bg_green,bg_red)
            old_bg = (bg_blue,bg_green,bg_red)
        if pen_thickness < 1:
            pen_thickness = 1
        ret, frame = cap.read()
        output = warpImage(frame)
        output = cv2.resize(output, (fw,fh))
        input_image = output.copy()
        low_b = np.uint8([255,255,255])
        high_b = np.uint8([255,255,255])
        mask = cv2.inRange(output, high_b, low_b)
        contours, heirarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        tool_color = str(pen_color)
        bgr_color = str((bg_blue,bg_green,bg_red))
        tool_size = str(pen_thickness)
        if len(contours) > 0 :
            c = max(contours, key=cv2.contourArea)
            M = cv2.moments(c)
            if M["m00"] !=0 :
                currX = int(M['m10']/M['m00'])
                currY = int(M['m01']/M['m00'])
                if prevX==0 and prevY==0:
                    prevX = currX
                    prevY = currY
                if tool_select == 0 and disable_tools == 0:
                    undo_option = "Available"
                    cv2.line(canvas, (prevX,prevY), (currX,currY), pen_color, pen_thickness)
                    all_values = (currX,currY),(prevX,prevY),pen_thickness,(bg_blue,bg_green,bg_red)
                    x_y_t.append(all_values)
                    undo_redo_index = len(x_y_t)-1
                    if len(x_y_t) > 250:
                        x_y_t.pop(0)
                        undo_redo_index = len(x_y_t)-1
                if tool_select == 1 and disable_tools == 0:
                    undo_option = "Not Available"
                    cv2.line(canvas, (prevX,prevY), (currX,currY), (bg_blue,bg_green,bg_red), pen_thickness)
                if tool_select == 2 and disable_tools == 0:
                    undo_option = "Not Available"
                    mask_fill = np.zeros([fh+2, fw+2], np.uint8)
                    cv2.floodFill(canvas, mask_fill, (currX,currY), pen_color, (100,100,100), (50,50,50), cv2.FLOODFILL_FIXED_RANGE)
                if disable_tools == -1:
                    undo_option = "Not Available"
                    if counter == 0:
                        canvas_copy = canvas.copy()
                        canvas_copy2 = canvas.copy()
                        counter = 1
                    if counter == 1:
                        canvas_copy3 = canvas_copy2
                        cv2.circle(canvas_copy3, (currX,currY), 3, cursor_color, cv2.FILLED)
                        canvas = canvas_copy3
                        cv2.imshow("WACKOM - MADE By Engineer Kid",canvas)
                        sleep(0.01)
                        canvas = canvas_copy2
                if disable_tools == 0:
                    canvas = canvas_copy
                    counter = 0
                prevX = currX
                prevY = currY
                #print("Detected")
        else:
            prevX = 0
            prevY = 0
            canvas = canvas_copy
            counter = 0
            #print("Not Detected")
        if disp_mainfeed == 1:
            display_ed = "Enabled"
            cv2.imshow("Main Feed",input_image)
        else:
            display_ed = "Disabled"
            cv2.destroyWindow("Main Feed")
        if info_var == 0:
            GetStatus()
        cv2.imshow("WACKOM - MADE By Engineer Kid",canvas)
        if disable_tools == 0:
            control_panel[0:39] = pen_color
            control_panel[59:100] = (bg_blue,bg_green,bg_red) 
        elif disable_tools == -1:
            control_panel[0:39] = cursor_color
            control_panel[59:100] = cursor_color 
        cv2.putText(control_panel, "Brush Color -",(5,30),cv2.FONT_HERSHEY_SIMPLEX,1,(bg_blue,bg_green,bg_red),2)
        cv2.putText(control_panel, "BGR Color -",(5,90),cv2.FONT_HERSHEY_SIMPLEX,1,pen_color,2)
        cv2.imshow("Control Panel", control_panel)

    key = cv2.waitKey(1) & 0xff
    if key == ord('z') and disable_tools == 0:
        if undo_redo_index > 0 and x_y_t[undo_redo_index][3] == (bg_blue,bg_green,bg_red):
            cv2.line(canvas, x_y_t[undo_redo_index][0], x_y_t[undo_redo_index][1], x_y_t[undo_redo_index][3], x_y_t[undo_redo_index][2])
            undo_redo_index -= 1   
        else:
            undo_redo_index = 0
            x_y_t.clear()
            all_values = 0
    if key == ord('b'):
        cv2.setTrackbarPos("B / E / F", "Control Panel",0)
        tool_sel = "Brush"
        GetStatus()
    if key == ord('e'):
        cv2.setTrackbarPos("B / E / F", "Control Panel",1)
        tool_sel = "Eraser"
        GetStatus()
    if key == ord('f'):
        cv2.setTrackbarPos("B / E / F", "Control Panel",2)
        tool_sel = "Bucket Fill"
        GetStatus()
    if key == ord('k'):
        disable_tools = ~ disable_tools
        if disable_tools == 0:
            tool_ed = "No"
        if disable_tools == -1:
            tool_ed = "Yes"
            tool_sel = "Not Available"
            undo_option = "Not Available"
        GetStatus()
    if key == ord('i'):
        info_var = ~info_var
    if key == 27:
        break
cap.release()
cv2.destroyAllWindows()
If you liked this little project then you can support me by subscribing my YouTube channel. Also, if you make this project or improve it then do let me know (if you wish). I would love to hear what features you have added. So, yeah this is it. Have a great time and stay creative :)