Advertisement
here2share

# Tk_png_steady_tracer_to_svg.py

Aug 10th, 2022 (edited)
843
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.71 KB | None | 0 0
  1. # Tk_png_steady_tracer_to_svg.py
  2.  
  3. # Had attempted to write this around 2010 as a beginner then figured maybe this was an impossible feat for Python
  4. # I still can't find a way to solve the few odd issues in it.
  5.  
  6. from tkinter import *
  7. from math import *
  8. import webbrowser
  9. from PIL import ImageTk, Image
  10. from tkinter import filedialog
  11. from time import *
  12.  
  13. def mouse_down(e):
  14.     cv.px = int(e.x/unit) * unit
  15.     cv.py = int(e.y/unit) * unit
  16. def mouse_up(e):
  17.     cv.x = 0
  18.     cv.y = 0
  19.     vectors.append(tmp_vectors[:])
  20.     tmp_vectors.clear()
  21. def mouse_move(e):
  22.     cv.x = e.x
  23.     cv.y = e.y
  24.     draw_lines()
  25. def scroll_dist():
  26.     dist = sqrt((cv.x - cv.width/2)**2 + (cv.y - cv.height/2)**2)
  27.     if 50 < dist:
  28.         cv.scrx = cv.scrx + (cv.x - cv.width/2)*0.00001
  29.         cv.scry = cv.scry + (cv.y - cv.height/2)*0.00001
  30.         canvas.xview_scroll(int(cv.scrx), 'units')
  31.         canvas.yview_scroll(int(cv.scry), 'units')
  32.         cv.scrx = cv.scrx - int(cv.scrx)
  33.         cv.scry = cv.scry - int(cv.scry)
  34.  
  35. # to resize (w,h) to fit the target area size
  36. def resize(w, h, target_area):
  37.     ratio = (target_area / (w * h))**0.5
  38.     return int(w * ratio), int(h * ratio)
  39.    
  40. def nearest():
  41.     cv.x = int(cv.x/unit) * unit
  42.     cv.y = int(cv.y/unit) * unit
  43.    
  44. def key_release(e):
  45.     key = e.keysym
  46.     print('---')
  47.     if key in ('Control_L'):
  48.         canvas.unbind('<B1-Motion>')
  49.         cv.scrx = 0
  50.         cv.scry = 0
  51. def key_press(e):
  52.     key = e.keysym
  53.     print('+++')
  54.     if key in ('Control_L'):
  55.         canvas.bind('<B1-Motion>', mouse_move)
  56.     elif key.lower() == 'x':
  57.         delete_selected_vector_points()
  58.     elif key.lower() == 't':
  59.         toggle_bezier_trace()
  60.  
  61. # draw lines with mouse drag that snaps to the grid as vector points
  62. def draw_lines():
  63.     scroll_dist()
  64.     if cv.x:
  65.         nearest()
  66.         if (cv.x, cv.y) not in vectors_occupy:
  67.             vectorize()
  68.             canvas.create_line(cv.px, cv.py, cv.x, cv.y, fill='black', tags=('mv','pts'))
  69.         cv.px = cv.x
  70.         cv.py = cv.y
  71.         # root.after(1, draw_lines) ### ??? glitches
  72.  
  73. def vectorize():
  74.     tmp_vectors.append((cv.x, cv.y))
  75.     vectors_occupy.append((cv.x, cv.y))
  76.     for xy in ((cv.x+1, cv.y),(cv.x, cv.y+1),(cv.x-1, cv.y),(cv.x, cv.y-1)):
  77.         vectors_occupy.append(xy)
  78. def delete_all():
  79.     canvas.delete('pts')
  80.     vectors.clear()
  81.     vectors_occupy.clear()
  82. def delete_selected_vector_points():
  83.     # delete when 'x' is press over a vector point
  84.     nearest()
  85.     for k,v in enumerate(vectors):
  86.         try:
  87.             v.remove((cv.x, cv.y))
  88.             vectors[k].remove((cv.x, cv.y))
  89.             break
  90.         except: 0
  91.     for xy in ((cv.x+1, cv.y),(cv.x, cv.y+1),(cv.x-1, cv.y),(cv.x, cv.y-1)):
  92.         try: vectors_occupy.remove(xy)
  93.         except: 0
  94.     # redraw every vector line
  95.     canvas.delete('pts')
  96.     for line in vectors:
  97.         canvas.create_line(line, fill='black', tags=('mv','pts'))
  98.  
  99. def bezier_curve(points):
  100.     n = len(points)
  101.     b = []
  102.     for t in range(0, 200):
  103.         t = t/200
  104.         x = y = 0
  105.         for i, p in enumerate(points):
  106.             x += p[0] * binomial(n, i) * (1 - t)**(n-i) * t**i
  107.             y += p[1] * binomial(n, i) * (1 - t)**(n-i) * t**i
  108.         b.append((x, y))
  109.     return b[:-1]+points[-2:-1]
  110.  
  111. def binomial(n, k):
  112.     if 0 <= k <= n:
  113.         n_tok = 1
  114.         k_tok = 1
  115.         for t in range(1, min(k, n - k) + 1):
  116.             n_tok *= n
  117.             k_tok *= t
  118.             n -= 1
  119.         return n_tok // k_tok
  120.     else:
  121.         return 0
  122.  
  123. def toggle_bezier_trace():
  124.     cv.trace = abs(cv.trace-1)
  125.     if cv.trace:
  126.         canvas.itemconfig('pts', fill='red')
  127.         svg()
  128.     else:
  129.         canvas.delete('svg')
  130.         canvas.itemconfig('pts', fill='black')
  131.  
  132. def draw_grid():
  133.     for x in range(0, sq_units*4, unit):
  134.         canvas.create_line([(x, 0), (x, sq_units*4)], fill='lightblue', tags=('mv','g'))
  135.     for y in range(0, sq_units*4, unit):
  136.         canvas.create_line([(0, y), (sq_units*4, y)], fill='lightblue', tags=('mv','g'))
  137.  
  138. def make_image_transparent():
  139.     # create transparent image
  140.     img = Image.new('RGBA', (cv.image.width, cv.image.height), (0, 0, 0, 0))
  141.     # create a pixel map
  142.     pixels = img.load()
  143.  
  144.     for x in range(cv.image.width):
  145.         for y in range(cv.image.height):
  146.             r, g, b = cv.image.getpixel((x, y))
  147.             # change pixel transparency
  148.             pixels[x, y] = (r, g, b, 60)
  149.     ww, hh = resize(cv.image.width, cv.image.height, sq_units*sq_units)
  150.     cv.image_resized = img.resize((ww, hh), Image.ANTIALIAS)
  151.    
  152.     delete_all()
  153.  
  154.     # place image
  155.     cv.image_on_canvas = ImageTk.PhotoImage(cv.image_resized)
  156.     canvas.create_image(120, 120, image=cv.image_on_canvas, anchor="nw", tag='mv')
  157.    
  158.     canvas.delete('g')
  159.     draw_grid()
  160.    
  161.     cv.width = ww
  162.     cv.height = hh
  163.    
  164.     canvas.config(scrollregion=(0,0,ww+240,hh+240))
  165.  
  166. def svg():
  167.     for line in vectors:
  168.         cv.svg_image = canvas.create_line(bezier_curve(line), fill='green', width=3, tags=('mv','svg'))
  169.  
  170. root = Tk()
  171. root.geometry('+-10+0')
  172. root.title("PNG to SVG Tracer")
  173.  
  174. class Cv(): 0
  175. cv = Cv()
  176.  
  177. cv.x = 0
  178. cv.y = 0
  179. cv.px = 0
  180. cv.py = 0
  181. cv.scrx = 0
  182. cv.scry = 0
  183. cv.trace = 0
  184. cv.filename = 0
  185. cv.image = 0
  186. cv.png = 0
  187. cv.image_on_canvas = 0
  188. cv.image_size = 0
  189. cv.svg_image = 0
  190. cv.prev = 0
  191. cv.timer = time()
  192.  
  193. unit = 20
  194.  
  195. cv.width = 1280
  196. cv.height = 640
  197.  
  198. sq_units = 4000
  199.  
  200. vectors = []
  201. vectors_occupy = [] # <<<<< Python ignores this?
  202. tmp_vectors = []
  203. frame = Frame(root)
  204. frame.pack()
  205.  
  206. # create canvas with scrollbars
  207. canvas = Canvas(frame, width=cv.width, height=cv.height, bg='white')
  208. canvas.pack(side=LEFT, expand=True, fill=BOTH)
  209. canvas.pack_propagate(False)
  210.  
  211. # needed to detect all the keyboard events <<<<<
  212. canvas.focus_set()
  213.  
  214. draw_grid()
  215.  
  216. # create scrollbars
  217. vbar = Scrollbar(canvas)
  218. vbar.pack(side=RIGHT, fill=Y)
  219. hbar = Scrollbar(canvas, orient=HORIZONTAL)
  220. hbar.pack(side=BOTTOM, fill=X)
  221.  
  222. # attach canvas to scrollbars
  223. canvas.config(xscrollcommand=hbar.set, yscrollcommand=vbar.set)
  224.  
  225. # bind scrollbars to the canvas
  226. hbar.config(command=canvas.xview)
  227. vbar.config(command=canvas.yview)
  228.  
  229. # set the scrollregion to all
  230. canvas.config(scrollregion=(0,0,sq_units,sq_units))
  231. '''
  232. '''
  233.  
  234. def load_image():
  235.     cv.filename = filedialog.askopenfilename(initialdir='/', title='Select file', filetypes=(('png files', '*.png'), ('all files', '*.*')))
  236.     cv.image = Image.open(cv.filename)
  237.     make_image_transparent()
  238.  
  239. def save_as_svg():
  240.     svg()
  241.     pad = 20
  242.     min_x = max_x = min_y = max_y = min(cv.width/2,cv.height/2)
  243.     for vector in sum(vectors, []):
  244.         if vector[0] < min_x:
  245.             min_x = vector[0]
  246.         if vector[1] < min_y:
  247.             min_y = vector[1]
  248.         if vector[0] > max_x:
  249.             max_x = vector[0]
  250.         if vector[1] > max_y:
  251.             max_y = vector[1]
  252.     cv.svg_width = int(max_x - min_x + pad)
  253.     cv.svg_height = int(max_y - min_y + pad)
  254.     cv.svg_x = -min_x + pad
  255.     cv.svg_y = -min_y + pad
  256.     # open new canvas then fit copy of padded cv.svg_image into it
  257.     cv.svg = Image.new('RGBA', (cv.svg_width, cv.svg_height), (0, 0, 0, 0))
  258.     # create a pixel map
  259.     pixels = cv.svg.load()
  260.     # copy cv.svg_image into new image
  261.     for item in canvas.find_withtag('svg')[1:]:
  262.         # r, g, b, a = cv.svg_image.getpixel((x, y))
  263.         x, y = canvas.coords(item)
  264.         x = int(x - cv.svg_x)
  265.         y = int(y - cv.svg_y)
  266.         # color = canvas.itemcget(item, 'fill')
  267.         pixels[x, y] = (0, 0, 0, 255)
  268.     # save new image
  269.     cv.svg.save(cv.filename.split('.')[0] + '.svg')
  270.     # open new image in browser
  271.     webbrowser.open(cv.filename.split('.')[0] + '.svg')
  272.    
  273.     downsized_image = cv.image.resize((cv.image.width, cv.image.height), Image.ANTIALIAS)
  274.  
  275.     # place image
  276.     cv.image_on_canvas = ImageTk.PhotoImage(downsized_image)
  277.     canvas.create_image(120, 120, image=cv.image_on_canvas, anchor="nw", tag='mv')
  278.  
  279. def add_vectors(e):
  280.     if not cv.prev:
  281.         cv.prev = canvas.find_withtag('selected')
  282.         L = len(cv.prev)
  283.         if L:
  284.             index = cv.prev.index(cv.prev)[-1]
  285.             canvas.create_oval(canvas.coords(cv.prev[index]), fill='darkblue', width=6, tags=('mv', 'tmp'))
  286.             if index:
  287.                 canvas.create_oval(canvas.coords(cv.prev[index-1]), fill='blue', tags=('mv', 'tmp'))
  288.             if index != L:
  289.                 canvas.create_oval(canvas.coords(cv.prev[index]), fill='blue', tags=('mv', 'tmp'))
  290.     # else if a blue dot gets selected, delete both and the line in between, then connect that point to the cursor motion, then the next line to the cv.prev[index]
  291.     elif canvas.itemcget(cv.prev[-1], 'fill') == 'blue':
  292.         nearest()
  293.         for vector in vectors:
  294.             try:
  295.                 cv.index = vector.index((cv.x, cv.y))
  296.                 canvas.delete('tmp')
  297.                 cv.prev = 0
  298.                 break
  299.             except: 0
  300.  
  301. # create buttons
  302. button_open = Button(canvas, text='Load Image', command=load_image)
  303. button_open.pack(side=LEFT, anchor=NW)
  304. button_save = Button(canvas, text='Save SVG', command=save_as_svg)
  305. button_save.pack(side=LEFT, anchor=NW)
  306. button_clear = Button(canvas, text='Clear', command=delete_all)
  307. button_clear.pack(side=LEFT, anchor=NW)
  308. button_clear = Button(canvas, text='Trace', command=toggle_bezier_trace)
  309. button_clear.pack(side=LEFT, anchor=NW)
  310.  
  311. canvas.bind("<KeyRelease>", key_release)
  312. canvas.bind("<KeyPress>", key_press)
  313.  
  314. canvas.bind("<Button-1>", mouse_down)
  315. canvas.bind("<B1-Motion>", mouse_move)
  316. canvas.bind("<ButtonRelease-1>", mouse_up)
  317.  
  318. root.mainloop()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement