error on line 1
###############################################################################
# python version of the ruler image generator
# by error on line 1 (erroronline.one)
# you find the possible user inputs at the bottom of the document. these are
# preset but you can uncomment the input-commands. this is the main module for
# terminal-use only. here happens the important stuff.
#
# refactored as of 2018-08-16 to fit as importable module for gui application
# refactored as of 2018-08-21 to support background image and mostly pep8
###############################################################################

from PIL import Image, ImageDraw
import re
import math
from itertools import cycle
import sys

###############################################################################
# preparation of input values
###############################################################################

def ConvertColorInput(userinput):
    # returns either rgb or color name
    if userinput[0] == "#":
        chunk = int((len(userinput)-1)/3)
        try:
            rgb = [int(userinput[i:i+chunk], 16) for i in range(1, len(userinput), chunk)]
        except:
            print("c'mon. please enter serious values.")
            exit()
    else:
        rgb = [int(i) for i in re.split(r"\D+", userinput)]
    return tuple(rgb)

class inputs():
    # creates an object with user inputs
    # that is easier transferable to other objects and functions
    def __init__(self, **kwargs):
        self.x, self.y=int(kwargs["dim"][0]), int(kwargs["dim"][1])
        z = math.sqrt(self.y**2+self.x**2)
        scale = {"c":(z/kwargs["dia"]/2.54, (10, 5)),
                "i":(z/kwargs["dia"], (16, 8))}
        self.res = scale[kwargs["scaleto"]][0]
        self.unit = scale[kwargs["scaleto"]][1]
        self.width, self.height = self.x/self.res, self.y/self.res
        self.rotate="n"
    def update(self,**kwargs):
        self.fgcolor = ConvertColorInput(kwargs["fgcolor"])
        self.bgcolor = ConvertColorInput(kwargs["bgcolor"])
        self.bgimg = kwargs["bgimg"]
        try: self.bgalpha = float(kwargs["bgalpha"])
        except ValueError: self.bgalpha = 0
        try: self.reducefibo = int(kwargs["reducefibo"])
        except ValueError: self.reducefibo = 0
        try: self.fontsize = float(kwargs["fontsize"])
        except ValueError: self.fontsize = 10
        self.rotate = kwargs["rotate"]
        if "fileformat" in kwargs:
            self.fileformat = kwargs["fileformat"]
        else:
            self.fileformat = "jpg"

###############################################################################
# function and class to define output coordinates for all the elements
###############################################################################

def ruler(ui):
    # returns list of x/y coordinates for drawing a ruler on the left side
    # this is a function because the ruler is nothing magic. 
    line, coords, y1=0, [], 0
    while y1<ui.y:
        label = ""
        if not line%ui.unit[0]:
            x1=25
            label = str(line//ui.unit[0])
        elif not line%ui.unit[1]:
            x1 = 15
        else:
            x1 = 10
        coords.append([label, (0, int(y1), x1, int(y1))])
        y1 += ui.res/ui.unit[0]
        line += 1
    return coords[0:]

class fibonacci():
    # creates an object with properties for fibonacci-spiral containing
    # coordinates, maximum dimension, etc. 
    def __init__(self, ui):
        # find maximum fibonacci areas fitting to screen regardless of
        # direction in cm or inch
        a, b, self.fibonaccis = 0, 1, []
        while b<max(ui.width,ui.height):
            a, b = b, a+b
            self.fibonaccis.append(a)
        self.fibonaccis.pop() # last element is already bigger that screen
        self.xstart, self.ystart, self.width, self.height = 0, 0, 0, 0
        self.findstart(ui, False)

    def findstart(self, ui, max=False):
        #find center to start with and define width and height of structure
        if not max:
            max = len(self.fibonaccis)
        startingpoint = {"n":(1, 0, 1, 2),
                        "y":(0, 0, 2, 3)} # rotate yes/no
        self.xstart = startingpoint[ui.rotate][0]
        self.ystart = startingpoint[ui.rotate][1]
        for xc in self.fibonaccis[startingpoint[ui.rotate][2]:max:4]:
            self.xstart += xc
        for yc in self.fibonaccis[startingpoint[ui.rotate][3]:max:4]:
            self.ystart += yc
        for xm in self.fibonaccis[startingpoint[ui.rotate][2]:max:2]:
            self.width += xm
        for ym in self.fibonaccis[startingpoint[ui.rotate][3]:max:2]:
            self.height += ym

    def create(self, ui, max=False):
        #returns a list of coordinates for drawing sqares in fibonacci manner
        if not max:
            max = len(self.fibonaccis)
        self.findstart(ui, max)
        #define pixel coordinates counterclockwise
        startingpoint = [(-1, 1, -1, 0, 180), (1, 1, 0, 1, 90),
                        (1, -1, 1, 0, 0), (-1, -1, 0, -1, 270)]
        if ui.rotate == "n": # shift once
            startingpoint.append(startingpoint.pop(0))
        def directions(startingpoint):
            for i in cycle(startingpoint):
                yield i
        dir = directions(startingpoint)    
        self.areas = []
        x2, y2 = (ui.width-self.xstart)*ui.res-1, self.ystart*ui.res
        for a in self.fibonaccis[:max]:
            curdir = next(dir)
            go = a * ui.res
            #coordinates for squares
            x1, x2 = x2, x2 + go*curdir[0]
            y1, y2 = y2, y2 + go*curdir[1]
            #coordinates for double size rectangle according to pillow.draw.arc
            x3, x4 = x1 - go*curdir[2], x2 + go*curdir[3]
            y3, y4 = y1 - go*curdir[3], y2 - go*curdir[2]
            #swap values to upperleft/lower right because of pillow.draw.arc
            if curdir[4] == 0:
                y3, y4 = y4, y3
            elif curdir[4] == 180:
                x3, x4 = x4, x3
            elif curdir[4] == 270:
                x3, x4, y3, y4 = x4, x3, y4, y3
            #coordinates for center of degree scale
            x5, y5 = x2 - go*curdir[2], y2 - go*curdir[3]
            
            # (label, square coordinates, additional arc coordinates for use
            # with pillow, startdegrees)
            self.areas.append([str(a), (int(x1), int(y1), int(x2), int(y2)),
                                (int(x3), int(y3), int(x4), int(y4)),
                                (int(x5), int(y5)), curdir[4]])
        return self.areas

    def degrees(self, ui, area = False):
        # returns list of x/y coordinates for drawing a degree scale to
        # fibonacci spiral in given area + numbers
        # this is a function because like the ruler it is nothing magic. 
        if not area:
            area = self.areas[-1]
        else:
            area = self.areas[area-1]

        #degree scale depending on given area
        xc, yc, radius = area[3][0], area[3][1], int(area[0])*ui.res
        #convert from pillow value (starting at 3 o"clock clockwise)
        startdeg = 360 - area[4]
        coords = []
        for deg in range(startdeg-90, startdeg, 5):
            cosinus = math.cos(math.radians(deg))
            sinus = math.sin(math.radians(deg))
            label = ""
            if deg % 3:
                length = 10
            else:
                length = 25
                label = str(startdeg-deg)
            x1, y1 = xc + cosinus*(radius-length), yc - sinus*(radius-length)
            x2, y2 = xc + cosinus*radius, yc - sinus*radius
            coords.append([label, (int(x1), int(y1), int(x2), int(y2))])
        return coords[1:]

###############################################################################
# initiation and processing of coordinates for drawing them to image
###############################################################################

def draw_img(ui):
    img = Image.new("RGB", (ui.x, ui.y), ui.bgcolor)

    draw = ImageDraw.Draw(img)
    fibo = fibonacci(ui)

    for c in ruler(ui):
        draw.line((c[1]), fill=ui.fgcolor)
        if c[0] != "":
            draw.text((c[1][2] + ui.fontsize*.5, c[1][3] - ui.fontsize*.5),
                        c[0], fill=ui.fgcolor)

    for c in fibo.create(ui, len(fibo.fibonaccis)-ui.reducefibo):
        draw.rectangle(c[1], fill=None, outline=ui.fgcolor)
        xlabel = c[1][0] + (c[1][2]-c[1][0]) / 2
        ylabel = c[1][1] + (c[1][3]-c[1][1]) / 2
        draw.text((xlabel, ylabel), c[0], fill=ui.fgcolor)
        draw.arc(c[2], c[4], c[4] + 90, fill=ui.fgcolor)
        
    def drawdegree(square, ui):
        for c in square:
            draw.line(c[1], fill=ui.fgcolor)
            if c[0] != "":
                draw.text((c[1][0] + ui.fontsize*.5,
                            c[1][1] - ui.fontsize*.5),
                            c[0], fill=ui.fgcolor)

    drawdegree(fibo.degrees(ui), ui)
        
    if ui.bgimg:
        #print(ui.bgimg)
        try:
            bg = Image.open(ui.bgimg)
            ui_ratio = ui.x / ui.y
            if ui.x / ui.y >= bg.width / bg.height:
                #print("i"ll take the we borders" )
                cropamount = bg.height-bg.width/ui_ratio
                x1, y1 = 0, cropamount//2
                x2, y2 = bg.width, bg.height-cropamount//2
            else:
                #print("i"ll take the ns borders" )
                cropamount = bg.width-bg.height*ui_ratio
                x1, y1 = cropamount//2, 0
                x2, y2 = bg.width-cropamount//2, bg.height
            
            bg = bg.transform((ui.x, ui.y), Image.QUAD,
                            (x1,y1,x1,y2,x2,y2,x2,y1)).convert("RGB")
            img = Image.blend(img, bg, ui.bgalpha)
        except FileNotFoundError:
            print("could not open background image file %s..."%(ui.bgimg))


    del draw 
    try: img.save("ruler." + ui.fileformat)
    except: print('nope')
    return True

###############################################################################
# user input and output settings, preparations and recommendations
###############################################################################

if __name__ == "__main__":
    print("this creates an image with ruler, areas, circles, degrees")
    print("type the screen dimensions...")
    # e.g. 720x1280 4.6" for xperia z5 compact
    # or 3200x1800 13.3" for lenovo yoga 2 pro 13
    dim,dia=[720,1280], 4.6 
    #dim,dia=[3200,1800], 13.3 #re.split(r"\D+",input("type screen pixel resolution (width/height): ")), float(input("type screen size in inch: "))
    print("type in desired scale... ")
    scaleto="c" #input("[c]m or [i]nch? ")

    ui=inputs(dim=dim,dia=dia,scaleto=scaleto)

    fibo=fibonacci(ui)
    reducefibo=0
    if fibo.width>ui.width or fibo.height>ui.height:
        print("the maximum amount of fibonacci squares is %d. it is recommended\
to rotate it by 90°. or you can reduce the number of squares."%len(fibo.fibonaccis))
        reducefibo=0 #int(input("reduce by 0, 1, 2, etc. or select rotation in next step: "))

    print("set direction of fibonacci areas... ")
    rotate="n" #input("rotate clockwise by 90 degrees [y]es / [n]o? ")

    print("type in colors as webcolors (#aabbcc) or rgb (126, 29, 254)... ")
    bgcol, fgcol="#000000","#ffffff" #input("type in background color: "), input("type in foreground color: ")

    print("specify background image (in same folder). image might be cropped... ")
    bgalpha, bgimg=0.8, "pythonlogo.png" #float(input("0.0 to 1.0 value for merging intensity: ")), input("filename or just [enter] to skip: ")

    #print("set font size... ")
    fontsize=18 #int(input("type in font-size in pixel: "))

    print("specify image format. recommendation: [png] for less colors, [jpg] \
for more colors (e.g. with use of background image)... ")
    fileformat="jpg" #input("enter format: "))


    ui.update(reducefibo=reducefibo, rotate=rotate, fgcolor=fgcol,
                bgcolor=bgcol, bgalpha=bgalpha, bgimg=bgimg, fontsize=fontsize,
                fileformat=fileformat)    
    
    if draw_img(ui):
        print("image ruler.%s saved"%(fileformat))
    else:
        print("image could not be saved")