text: # from random import *
  # import math
  import argparse
  from PIL import Image, ImageDraw, ImageOps
  # from filters import *
  import filters
  # from strokesort import *
  import strokesort
  import perlin
  # from util import *
  import util
  
  
  no_cv = False  # whether to switch to no_cv mode
  export_path = "output/out.svg"  # default output path
  draw_contours = True  # whether to draw boundaries
  draw_hatch = True  # wheher to draw inside
  show_bitmap = False
  resolution = 1024
  hatch_size = 16
  contour_simplify = 2
  
  try:
      import numpy as np
      import cv2
  except Exception:  # need detailed exceptions
      print("Cannot import numpy/openCV. Switching to NO_CV mode.")
      no_cv = True
  
  
  def find_edges(IM):
      print("finding edges...")
      if no_cv:
          # appmask(IM,[F_Blur])
          filters.appmask(IM, [filters.F_SobelX, filters.F_SobelY])
      else:
          im = np.array(IM)
          im = cv2.GaussianBlur(im, (3, 3), 0)
          im = cv2.Canny(im, 100, 200)
          IM = Image.fromarray(im)
      return IM.point(lambda p: p > 128 and 255)
  
  
  def getdots(IM):
      print("getting contour points...")
      PX = IM.load()
      dots = []
      w, h = IM.size
      for y in range(h - 1):
          row = []
          for x in range(1, w):
              if PX[x, y] == 255:
                  if len(row) > 0:
                      if x - row[-1][0] == row[-1][-1] + 1:
                          row[-1] = (row[-1][0], row[-1][-1] + 1)
                      else:
                          row.append((x, 0))
                  else:
                      row.append((x, 0))
          dots.append(row)
      return dots
  
  
  def connectdots(dots):
      print("connecting contour points...")
      contours = []
      for y in range(len(dots)):
          for x, v in dots[y]:
              if v > -1:
                  if y == 0:
                      contours.append([(x, y)])
                  else:
                      closest = -1
                      cdist = 100
                      for x0, v0 in dots[y - 1]:
                          if abs(x0 - x) < cdist:
                              cdist = abs(x0 - x)
                              closest = x0
  
                      if cdist > 3:
                          contours.append([(x, y)])
                      else:
                          found = 0
                          for i in range(len(contours)):
                              if contours[i][-1] == (closest, y - 1):
                                  contours[i].append((
                                      x,
                                      y,
                                  ))
                                  found = 1
                                  break
                          if found == 0:
                              contours.append([(x, y)])
          for c in contours:
              if c[-1][1] < y - 1 and len(c) < 4:
                  contours.remove(c)
      return contours
  
  
  def getcontours(IM, sc=2):
      print("generating contours...")
      IM = find_edges(IM)
      IM1 = IM.copy()
      IM2 = IM.rotate(-90, expand=True).transpose(Image.FLIP_LEFT_RIGHT)
      dots1 = getdots(IM1)
      contours1 = connectdots(dots1)
      dots2 = getdots(IM2)
      contours2 = connectdots(dots2)
  
      for i in range(len(contours2)):
          contours2[i] = [(c[1], c[0]) for c in contours2[i]]
      contours = contours1 + contours2
  
      for i in range(len(contours)):
          for j in range(len(contours)):
              if len(contours[i]) > 0 and len(contours[j]) > 0:
                  if util.distsum(contours[j][0], contours[i][-1]) < 8:
                      contours[i] = contours[i] + contours[j]
                      contours[j] = []
  
      for i in range(len(contours)):
          contours[i] = [contours[i][j] for j in range(0, len(contours[i]), 8)]
  
      contours = [c for c in contours if len(c) > 1]
  
      for i in range(0, len(contours)):
          contours[i] = [(v[0] * sc, v[1] * sc) for v in contours[i]]
  
      for i in range(0, len(contours)):
          for j in range(0, len(contours[i])):
              contours[i][j] = int(contours[i][j][0] +
                                   10 * perlin.noise(i * 0.5, j * 0.1, 1)), int(
                                       contours[i][j][1] +
                                       10 * perlin.noise(i * 0.5, j * 0.1, 2))
  
      return contours
  
  
  def hatch(IM, sc=16):
      print("hatching...")
      PX = IM.load()
      w, h = IM.size
      lg1 = []
      lg2 = []
      for x0 in range(w):
          for y0 in range(h):
              x = x0 * sc
              y = y0 * sc
              if PX[x0, y0] > 144:
                  pass
  
              elif PX[x0, y0] > 64:
                  lg1.append([(x, y + sc / 4), (x + sc, y + sc / 4)])
              elif PX[x0, y0] > 16:
                  lg1.append([(x, y + sc / 4), (x + sc, y + sc / 4)])
                  lg2.append([(x + sc, y), (x, y + sc)])
  
              else:
                  lg1.append([(x, y + sc / 4), (x + sc, y + sc / 4)])
                  lg1.append([(x, y + sc / 2 + sc / 4),
                              (x + sc, y + sc / 2 + sc / 4)])
                  lg2.append([(x + sc, y), (x, y + sc)])
  
      lines = [lg1, lg2]
      for k in range(0, len(lines)):
          for i in range(0, len(lines[k])):
              for j in range(0, len(lines[k])):
                  if lines[k][i] != [] and lines[k][j] != []:
                      if lines[k][i][-1] == lines[k][j][0]:
                          lines[k][i] = lines[k][i] + lines[k][j][1:]
                          lines[k][j] = []
          lines[k] = [l for l in lines[k] if len(l) > 0]
      lines = lines[0] + lines[1]
  
      for i in range(0, len(lines)):
          for j in range(0, len(lines[i])):
              lines[i][j] = int(
                  lines[i][j][0] + sc * perlin.noise(i * 0.5, j * 0.1, 1)
              ), int(lines[i][j][1] + sc * perlin.noise(i * 0.5, j * 0.1, 2)) - j
      return lines
  
  
  def sketch(path):
      IM = None
      imgAvailable = False
      # print("Received path " + path)
      possible = [  # for input tolerance
          path, "images/" + path, "images/" + path + ".jpg",
          "images/" + path + ".png", "images/" + path + ".tif"
      ]
      for p in possible:
          try:
              IM = Image.open(p)
              # break
          except FileNotFoundError:
              # print("The Input File wasn't found. Check Path")
              # exit(0)
              continue
          imgAvailable = True
      if not imgAvailable:
          print("The Input File wasn't found. Check Path")
          exit(0)
      w, h = IM.size
  
      IM = IM.convert("L")  # greyscale
      IM = ImageOps.autocontrast(IM, 10)
  
      lines = []
      # get lines
      if draw_contours:
          lines += getcontours(
              IM.resize((
                  resolution // contour_simplify,  # width 512
                  resolution // contour_simplify * h //
                  w)),  # height 512 * h // w
              contour_simplify)
      if draw_hatch:
          lines += hatch(
              IM.resize(
                  (resolution // hatch_size, resolution // hatch_size * h // w)),
              hatch_size)
      # sort lines for quicker drawing
      lines = strokesort.sortlines(lines)
      if show_bitmap:  # show png preview
          disp = Image.new("RGB", (resolution, resolution * h // w),
                           (255, 255, 255))
          draw = ImageDraw.Draw(disp)
          for l in lines:
              draw.line(l, (0, 0, 0), 5)
          disp.show()
  
      f = open(export_path, 'w')
      f.write(makesvg(lines))
      f.close()
      print(len(lines), "strokes.")
      print("done.")
      return lines
  
  
  def makesvg(lines):  # svg format?
      print("generating svg file...")
      out = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">'
      for line in lines:
          line = ",".join(
              [str(p[0] * 0.5) + "," + str(p[1] * 0.5) for p in line])
          out += '<polyline points="' + line + '" stroke="black" stroke-width="2" fill="none" />\n'
      out += '</svg>'
      return out
  
  
  if __name__ == "__main__":
      parser = argparse.ArgumentParser(
          description='Convert image to vectorized line drawing for plotters.')
      parser.add_argument('-i',
                          '--input',
                          dest='input_path',
                          default='lenna',
                          action='store',
                          nargs='?',
                          type=str,
                          help='Input path')
  
      parser.add_argument('-o',
                          '--output',
                          dest='output_path',
                          default=export_path,
                          action='store',
                          nargs='?',
                          type=str,
                          help='Output path.')
  
      parser.add_argument('-b',
                          '--show_bitmap',
                          dest='show_bitmap',
                          const=not show_bitmap,
                          default=show_bitmap,
                          action='store_const',
                          help="Display bitmap preview.")
  
      parser.add_argument('-nc',
                          '--no_contour',
                          dest='no_contour',
                          const=draw_contours,
                          default=not draw_contours,
                          action='store_const',
                          help="Don't draw contours.")
  
      parser.add_argument('-nh',
                          '--no_hatch',
                          dest='no_hatch',
                          const=draw_hatch,
                          default=not draw_hatch,
                          action='store_const',
                          help='Disable hatching.')
  
      parser.add_argument('--no_cv',
                          dest='no_cv',
                          const=not no_cv,
                          default=no_cv,
                          action='store_const',
                          help="Don't use openCV.")
  
      parser.add_argument('--hatch_size',
                          dest='hatch_size',
                          default=hatch_size,
                          action='store',
                          nargs='?',
                          type=int,
                          help='Patch size of hatches. eg. 8, 16, 32')
      parser.add_argument('--contour_simplify',
                          dest='contour_simplify',
                          default=contour_simplify,
                          action='store',
                          nargs='?',
                          type=int,
                          help='Level of contour simplification. eg. 1, 2, 3')
  
      args = parser.parse_args()
      # get args
      export_path = args.output_path
      draw_hatch = not args.no_hatch
      draw_contours = not args.no_contour
      hatch_size = args.hatch_size
      contour_simplify = args.contour_simplify
      show_bitmap = args.show_bitmap
      no_cv = args.no_cv
      sketch(args.input_path)
  