├── .gitignore ├── Colors.py ├── DrawBot.py ├── GetColorPositions.py ├── PixelData.py ├── README.md └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | positions.txt -------------------------------------------------------------------------------- /Colors.py: -------------------------------------------------------------------------------- 1 | allColors = [] 2 | 3 | class Color: 4 | def __init__(self, name, r, g, b): 5 | self.name = name 6 | self.R = r 7 | self.G = g 8 | self.B = b 9 | self.RGB = (self.R, self.G, self.B) 10 | self.x = int() 11 | self.y = int() 12 | allColors.append(self) 13 | 14 | def printData(self): 15 | print(f"{self.name}: X: {self.x} Y: {self.y}") 16 | 17 | black = Color("Black",0,0,0) 18 | gray = Color("Gray",102,102,102) 19 | blue = Color("Blue",0,80,205) 20 | white = Color("White",255,255,255) 21 | lightGray = Color("Light Gray",170,170,170) 22 | lightBlue = Color("Light Blue",38,201,201) 23 | green = Color("Green",1,116,32) 24 | brown = Color("Brown",105,21,6) 25 | lightBrown = Color("Light Blue",150,65,18) 26 | lightGreen = Color("Light Green",17, 176, 60) 27 | red = Color("Red",255,0,19) 28 | orange = Color("Orange",255,120,41) 29 | uglyBrown = Color("Ugly Brown",176,112,28) 30 | purple = Color("Pruple",153,0,78) 31 | skin = Color("Skin Color",203,90,87) 32 | yellow = Color("Yellow",255,193,38) 33 | pink = Color("Pink",255,0,143) 34 | lightPink = Color("Light Pink",254,175,168) -------------------------------------------------------------------------------- /DrawBot.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import time 3 | import mouse 4 | from mouse import LEFT, DOWN, get_position, wait 5 | import threading 6 | from Colors import * 7 | from PIL import Image 8 | from PIL import ImageEnhance 9 | from io import BytesIO 10 | import requests 11 | from urllib.request import Request, urlopen 12 | import keyboard 13 | import os 14 | import random 15 | from tkinter import filedialog 16 | from PixelData import * 17 | from math import sqrt 18 | import ctypes 19 | 20 | scalingFactor = ctypes.windll.shcore.GetScaleFactorForDevice(0) / 100 # Gets windows screen scale 21 | #drawingArea = (10,10) 22 | corner1 = (2998, 304) 23 | corner2 = (3707, 683) 24 | fileName = str() 25 | stopDrawing = False 26 | 27 | def LoadColorPositions(): 28 | try: 29 | lineNumber = 0 30 | file = open("positions.txt", "r") 31 | fileLines = file.readlines() 32 | 33 | for color in allColors: 34 | color.x = int(fileLines[lineNumber].split("\n")[0]) 35 | lineNumber += 1 36 | color.y = int(fileLines[lineNumber].split("\n")[0]) 37 | lineNumber += 1 38 | 39 | if (len(fileLines) != 36): 40 | statusLabel.config(text=f"Position load failed :( (Have you saved positions yet?)") 41 | else: 42 | statusLabel.config(text=f"Position load successful :)") 43 | 44 | file.close() 45 | 46 | except Exception as e: 47 | statusLabel.config(text=f"Position load failed :(Have you saved positions yet?{e}") 48 | 49 | def DotPlace(): 50 | global stopDrawing 51 | stopDrawing = False 52 | img = 0 53 | detailLevel = 10 - int(detailLevelEntry.get()) + 1 54 | pixelCount = 200 55 | drawingArea = (abs(corner1[0] - corner2[0]), abs(corner1[1] - corner2[1])) 56 | xAddAmount = drawingArea[0] / pixelCount * detailLevel 57 | yAddAmount = drawingArea[1] / pixelCount * detailLevel 58 | 59 | #print(int(drawingArea[0] * drawingArea[1] / 5)) 60 | 61 | if (imageMode.get() == 1 or imageMode.get() == 0): 62 | response = requests.get(urlInput.get()) 63 | #response = requests.get("https://cdn.discordapp.com/attachments/374695917170851840/895139808895205487/Screenshot-2021-10-05-205602.png") 64 | #img = Image.open(BytesIO(response.content)) 65 | img = ImageEnhance.Sharpness(Image.open(BytesIO(response.content)).convert('RGBA')).image 66 | else: 67 | print(fileName) 68 | img = ImageEnhance.Sharpness(Image.open(fileName).convert('RGBA')).image 69 | 70 | #img = ImageEnhance.Sharpness (Image.open("unnamed.png").convert('RGBA')).image 71 | #edges = img.filter(ImageFilter.FIND_EDGES) 72 | img = img.resize( (int(pixelCount / detailLevel), int(pixelCount / detailLevel)) ) 73 | pix = img.load() 74 | print(pix) 75 | 76 | xSize, ySize = img.size 77 | lastColor = "Name" 78 | 79 | for x in range(int(xSize)): 80 | for y in range(int(ySize)): 81 | if (stopDrawing): # if esc is pushed, quit drawing 82 | return 83 | 84 | colorIndex = FindClosestRGB(pix[x, y]) # Get color of pixel 85 | 86 | if (pix[x, y][3] == 0 or allColors[colorIndex].name == "White"): # if pixel is transparent or white, skip 87 | continue 88 | 89 | time.sleep(0.001) # Pause before drawing again 90 | 91 | if (allColors[colorIndex].name != lastColor): 92 | # Account for windows screen scaling when moving 93 | mouse.move(allColors[colorIndex].x / scalingFactor, allColors[colorIndex].y / scalingFactor, duration=0) 94 | mouse.click(LEFT) 95 | 96 | # mouse.move(corner1[0]+x*detailLevel,corner1[1]+y*detailLevel,duration=0) 97 | mouse.move(corner1[0] + xAddAmount * x, corner1[1] + y * yAddAmount, duration=0) 98 | mouse.click(LEFT) 99 | lastColor = allColors[colorIndex].name 100 | 101 | mouse.move(white.x, white.y) 102 | mouse.click(LEFT) 103 | pix[0, 0] = (75, 22, 255) 104 | 105 | def LinePlace(): 106 | global stopDrawing 107 | stopDrawing = False 108 | img = 0 109 | detailLevel = 10 - int(detailLevelEntry.get()) + 1 110 | pixelCount = 200 111 | drawingArea = (abs(corner1[0] - corner2[0]), abs(corner1[1] - corner2[1])) 112 | xAddAmount = drawingArea[0] / pixelCount * detailLevel 113 | yAddAmount = drawingArea[1] / pixelCount * detailLevel 114 | 115 | if (imageMode.get() == 1 or imageMode.get() == 0): 116 | response = requests.get(urlInput.get()) 117 | #response = requests.get("https://cdn.discordapp.com/attachments/374695917170851840/895139808895205487/Screenshot-2021-10-05-205602.png") 118 | #img = Image.open(BytesIO(response.content)) 119 | img = ImageEnhance.Sharpness(Image.open(BytesIO(response.content)).convert('RGBA')).image 120 | else: 121 | print(fileName) 122 | img = ImageEnhance.Sharpness(Image.open(fileName).convert('RGBA')).image 123 | 124 | #img = ImageEnhance.Sharpness (Image.open("unnamed.png").convert('RGBA')).image 125 | #edges = img.filter(ImageFilter.FIND_EDGES) 126 | img = img.resize((int(pixelCount / detailLevel), int(pixelCount / detailLevel))) 127 | pix = img.load() 128 | print(pix) 129 | 130 | xSize, ySize = img.size 131 | lastColor = "Name" 132 | allPixelData = [] 133 | 134 | for x in range(int(xSize)): 135 | for y in range(int(ySize)): 136 | if (stopDrawing): 137 | return 138 | 139 | if (pix[x, y][3] == 0): 140 | continue 141 | 142 | color = FindClosestRGB(pix[x, y]) 143 | 144 | if (allColors[color].name == "White"): 145 | continue 146 | 147 | allPixelData.append(PixelData(x, y, allColors[color])) 148 | 149 | length = len(allPixelData) 150 | print(len(allPixelData)) 151 | 152 | currentX = 0 153 | 154 | def DrawFromTo(start, end, color: Color): 155 | # Account for screen scaling factor 156 | mouse.move(color.x / scalingFactor, color.y / scalingFactor) 157 | mouse.click() 158 | # mouse.drag(corner1[0]+xAddAmount*start[0],corner1[1]+yAddAmount*start[1],corner1[0]+xAddAmount*end[0],corner1[1]+yAddAmount*end[1]) 159 | mouse.move(corner1[0] + xAddAmount * start[0], 160 | corner1[1] + yAddAmount * start[1]) 161 | mouse.hold() 162 | mouse.move(corner1[0] + xAddAmount * end[0], corner1[1] + yAddAmount * end[1]) 163 | time.sleep(0.001) # Pause before drawing again 164 | mouse.release() 165 | 166 | def remove_values_from_list(the_list, val): 167 | return [value for value in the_list if value.x != val] 168 | 169 | while True: 170 | thisX = [] 171 | for i in allPixelData: 172 | if (i.x != currentX): 173 | allPixelData = remove_values_from_list(allPixelData, currentX) 174 | currentX += 1 175 | break 176 | 177 | thisX.append(i) 178 | 179 | if (currentX == pixelCount/detailLevel - 1): 180 | break 181 | 182 | startData: PixelData = PixelData 183 | for i in range(len(thisX)): 184 | if (i == 0): 185 | startData = thisX[i] 186 | continue 187 | 188 | if (thisX[i].color.name != startData.color.name) or i == len(thisX) - 1: 189 | if(stopDrawing): 190 | return 191 | 192 | DrawFromTo((startData.x, startData.y), (thisX[i - 1].x, thisX[i - 1].y), startData.color) 193 | 194 | startData = thisX[i] 195 | 196 | if(len(allPixelData) == 0): 197 | break 198 | 199 | def DrawImage(): 200 | if(drawMode.get() == 0): 201 | DotPlace() 202 | else: 203 | LinePlace() 204 | 205 | def SetDrawingBoundary(): 206 | global drawingArea 207 | global corner1, corner2 208 | statusLabel.config(text="Click top left corner") 209 | mouse.wait(LEFT, mouse.DOWN) 210 | corner1 = get_position() 211 | statusLabel.config(text="Click bottom right corner") 212 | mouse.wait(LEFT, mouse.DOWN) 213 | corner2 = get_position() 214 | x = abs(corner1[0] - corner2[0]) 215 | y = abs(corner1[0] - corner2[1]) 216 | drawingArea = (x, y) 217 | print("Drawing Area:", drawingArea) 218 | statusLabel.config(text="Drawing Area Set") 219 | 220 | def FindClosestRGB(rgb: tuple): 221 | values = list() 222 | 223 | for color in allColors: 224 | number = 0 225 | number += abs(rgb[0] - color.RGB[0]) 226 | number += abs(rgb[1] - color.RGB[1]) 227 | number += abs(rgb[2] - color.RGB[2]) 228 | 229 | red = pow(rgb[0] - color.RGB[0], 2) 230 | green = pow(rgb[1] - color.RGB[1], 2) 231 | blue = pow(rgb[2] - color.RGB[2], 2) 232 | number = sqrt(red + green + blue) 233 | values.append(number) 234 | 235 | index_min = min(range(len(values)), key=values.__getitem__) 236 | #print(allColors[index_min].name) 237 | return index_min 238 | 239 | def ChangeImageMode(): 240 | if(imageMode.get() == 1): 241 | urlInput.pack() 242 | localFileButton.forget() 243 | elif(imageMode.get() == 2): 244 | urlInput.forget() 245 | localFileButton.pack() 246 | 247 | def OpenFile(): 248 | global fileName 249 | fileName = filedialog.askopenfilename(initialdir="", filetypes=[( 250 | "PNG", "*.png"), ("JPG", "*.jpg"), ("JPEG", "*.jpeg")]) 251 | print(fileName) 252 | 253 | def Exit(): 254 | global stopDrawing 255 | stopDrawing = True 256 | print("Exiting") 257 | #os._exit(0) 258 | 259 | # GUI 260 | root = tk.Tk() 261 | #slowMode = tk.BooleanVar() 262 | slowMode = True 263 | imageMode = tk.IntVar() 264 | drawMode = tk.IntVar() 265 | root.geometry("800x600") 266 | root.title("Gartic Drawbot") 267 | 268 | tk.Radiobutton(root, 269 | text="URL", 270 | padx=20, 271 | variable=imageMode, 272 | command=ChangeImageMode, 273 | value=1).pack() 274 | 275 | tk.Radiobutton(root, 276 | text="Local File", 277 | padx=20, 278 | command=ChangeImageMode, 279 | variable=imageMode, 280 | value=2).pack() 281 | 282 | tk.Radiobutton(root, 283 | text="Dot Placement", 284 | padx=20, 285 | variable=drawMode, 286 | command=ChangeImageMode, 287 | value=0).pack() 288 | 289 | tk.Radiobutton(root, 290 | text="Line Placement", 291 | padx=20, 292 | command=ChangeImageMode, 293 | variable=drawMode, 294 | value=1).pack() 295 | 296 | tk.Button(root, text="Set drawing boundary", command=lambda: threading.Thread( 297 | target=SetDrawingBoundary).start()).pack() 298 | 299 | tk.Button(root, text="Draw Image", command=DrawImage).pack() 300 | 301 | detailLevelEntry = tk.Entry(root) 302 | tk.Label(root, text="Detail Level 1-10").pack() 303 | 304 | detailLevelEntry.insert(0, "9") 305 | detailLevelEntry.pack() 306 | #slowModeCheck = tk.Checkbutton(text="Slow Mode", variable=slowMode) 307 | #slowModeCheck.pack() 308 | statusLabel = tk.Label(text="Hello!") 309 | statusLabel.pack() 310 | LoadColorPositions() 311 | urlInput = tk.Entry(root) 312 | urlInput.pack() 313 | localFileButton = tk.Button(root, text="Open File", command=OpenFile) 314 | keyboard.add_hotkey("esc", Exit) 315 | root.mainloop() -------------------------------------------------------------------------------- /GetColorPositions.py: -------------------------------------------------------------------------------- 1 | from Colors import * 2 | import mouse 3 | from mouse import LEFT 4 | import pyautogui 5 | import tkinter as tk 6 | import threading 7 | 8 | def SetColorPositions(): 9 | colorToChange = black 10 | labelText = "Click the black Color" 11 | 12 | for i in range(3): 13 | if (i == 1): 14 | labelText = "Click the gray Color (to the right of black)" 15 | colorToChange=gray 16 | 17 | if (i == 2): 18 | labelText = "Click the white Color (below black)" 19 | colorToChange=white 20 | print("Checking for white") 21 | 22 | statusLabel.config(text=labelText) 23 | mouse.wait(LEFT, mouse.DOWN) 24 | position = pyautogui.position() 25 | statusLabel.config(text=position) 26 | colorToChange.x = position.x 27 | colorToChange.y = position.y 28 | 29 | xOffset = abs(black.x - gray.x) 30 | yOffset = 0 31 | 32 | for color, loopCount in zip(allColors, range(3, len(allColors) + 3)): 33 | if (loopCount % 3 == 0): 34 | color.x = black.x 35 | color.y = black.y + yOffset 36 | 37 | if (loopCount % 3 == 1): 38 | color.x = black.x + xOffset 39 | color.y = black.y + yOffset 40 | 41 | if (loopCount % 3 == 2): 42 | color.x = black.x + xOffset * 2 43 | color.y = black.y + yOffset 44 | print("Offset is: ",black.y - white.y) 45 | yOffset += abs(black.y - white.y) 46 | 47 | for color in allColors: 48 | color.printData() 49 | 50 | def GetPosition(): 51 | mouse.wait(LEFT, mouse.DOWN) 52 | print(pyautogui.position()) 53 | 54 | def DrawTest(): 55 | for color in allColors: 56 | mouse.move(color.x, color.y) 57 | mouse.click() 58 | mouse.move(color.x + 227, color.y) 59 | mouse.click() 60 | 61 | def SaveColorPositions(): 62 | try: 63 | LB = "\n" 64 | file = open("positions.txt","w") 65 | for color in allColors: 66 | file.write(str(color.x) +"\n" + str(color.y)+"\n") 67 | statusLabel.config(text="Save successful :)") 68 | 69 | except Exception as e: 70 | statusLabel.config(text=f"Save failed :({e}") 71 | 72 | finally: 73 | file.close() 74 | 75 | def LoadColorPositions(): 76 | try: 77 | lineNumber = 0 78 | file = open("positions.txt","r") 79 | fileLines = file.readlines() 80 | 81 | for color in allColors: 82 | color.x = int(fileLines[lineNumber].split("\n")[0]) 83 | lineNumber += 1 84 | color.y = int(fileLines[lineNumber].split("\n")[0]) 85 | lineNumber += 1 86 | 87 | if (len(fileLines) != 36): 88 | statusLabel.config(text=f"Position load failed :( (Have you saved positions yet?)") 89 | else: 90 | statusLabel.config(text=f"Position load successful :)") 91 | 92 | except Exception as e: 93 | statusLabel.config(text=f"Position load failed :({e}") 94 | 95 | finally: 96 | file.close() 97 | 98 | root = tk.Tk() 99 | root.geometry("800x600") 100 | root.title("Set Color Positions") 101 | 102 | tk.Button(root, text="Set Color Positions", command=lambda: threading.Thread( 103 | target=SetColorPositions).start()).pack() 104 | 105 | tk.Button(root, text="Get mouse position", 106 | command=lambda: threading.Thread(target=GetPosition).start()).pack() 107 | 108 | tk.Button(root, text="Do color test",command=lambda: threading.Thread(target=DrawTest).start()).pack() 109 | 110 | tk.Button(root, text="Save color positions",command=lambda: threading.Thread(target=SaveColorPositions).start()).pack() 111 | 112 | tk.Button(root, text="Load color positions",command=lambda: threading.Thread(target=LoadColorPositions).start()).pack() 113 | 114 | statusLabel = tk.Label(text="Hello!") 115 | statusLabel.pack() 116 | root.mainloop() 117 | -------------------------------------------------------------------------------- /PixelData.py: -------------------------------------------------------------------------------- 1 | class PixelData: 2 | def __init__(self, x, y, color): 3 | self.x = x 4 | self.y = y 5 | self.color = color 6 | 7 | def print(self): 8 | print(self.x, self.y, self.color) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gartic-Phone-Draw-Bot 2 | Drawing Bot for Gartic Phone 3 | The code if very messy, especially the function for line drawing. Kinda just winged it and hoped it worked. 4 | If at any time the program won't start clicking or clicks randomly just press esc to kill the program 5 | 6 | 7 | For a video demostrating how to run the program goto https://www.youtube.com/watch?v=gfKDNLLBOIs. 8 | (Thanks to ToastCart for his feedback!) 9 | Instructions: 10 | Run GetColorPositions.exe and click set color positions. Click the black color, then the gray color, the the white color (below the black). Click test color positions and see if all the colors match up. If they don't, then try again. Finally, click save color positions. 11 | 12 | Open DrawBot.exe and either paste in an image you want drawn or select a local file. Then Click set drawing boundary to set where the image should be drawn and finally click Draw! 13 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CowCoding0/Gartic-Phone-Draw-Bot/99820afc88799b78a9bca7ede47bee15ea06ef44/requirements.txt --------------------------------------------------------------------------------