""" 
MOSAICS WITH A GENETIC ALGORITHM IN PYTHON+TKINTER
By: Aldo Gonzalez
Saint Norbert College Senior Capstone Project - Spring 2021

Sources:
-Pillow
-Slicer
-L subclass of list https://code.activestate.com/recipes/579103-python-addset-attributes-to-list/ 
-W3schools
-tutorialspoint
-Codemy (Tkinter tutorial videos)
-htmlview https://pypi.org/project/tkhtmlview/
-Dr. McVey
"""

import copy
import random
import time
import os
from tkinter import *
import PIL
from PIL import ImageTk,Image,ImageOps #need to include all modules
import image_slicer
#import io
#import zipfile
from tkhtmlview import HTMLLabel


root = Tk() #window
root.title('Mosaics')
#Frame.geometry("1000x850") #window size?

Frame=LabelFrame(root)
Frame.pack() 

global ps1, ps2 #packetsize vars in fitness -> determine #s that go into calculating packetsize

ps1=1 #if 5 (this is kinda the half pt, vs ps2 is the full length -> assuming same w+l, so can only do 4x4,10x10,etc.)
ps2=2 #+10 -> 10x10 packets=100=500. good for basics w/ 25. probs need less w/ 49t (1500px)
#back w/ 500/3750=13%. could stick around that
#400t=300px= 4x4=26%, or 2x2=.7%
#note: keep even bc =/= half pixels

class L(list):
    """
    A subclass of list that can accept additional attributes.
    Should be able to be used just like a regular list.

    The problem:
    a = [1, 2, 4, 8]
    a.x = "Hey!" # AttributeError: 'list' object has no attribute 'x'

    The solution:
    a = L(1, 2, 4, 8)
    a.x = "Hey!"
    print a       # [1, 2, 4, 8]
    print a.x     # "Hey!"
    print len(a)  # 4

    You can also do these:
    a = L( 1, 2, 4, 8 , x="Hey!" )                 # [1, 2, 4, 8]
    a = L( 1, 2, 4, 8 )( x="Hey!" )                # [1, 2, 4, 8]
    a = L( [1, 2, 4, 8] , x="Hey!" )               # [1, 2, 4, 8]
    a = L( {1, 2, 4, 8} , x="Hey!" )               # [1, 2, 4, 8]
    a = L( [2 ** b for b in range(4)] , x="Hey!" ) # [1, 2, 4, 8]
    a = L( (2 ** b for b in range(4)) , x="Hey!" ) # [1, 2, 4, 8]
    a = L( 2 ** b for b in range(4) )( x="Hey!" )  # [1, 2, 4, 8]
    a = L( 2 )                                     # [2]
    """
    def __new__(self, *args, **kwargs):
        return super(L, self).__new__(self, args, kwargs)

    def __init__(self, *args, **kwargs):
        if len(args) == 1 and hasattr(args[0], '__iter__'):
            list.__init__(self, args[0])
        else:
            list.__init__(self, args)
        self.__dict__.update(kwargs)

    def __call__(self, **kwargs):
        self.__dict__.update(kwargs)
        return self


""" 
IMGINIT
-input parameters -> globals
-multi or single mode set (via a and adv vars)
-slicer slices into tiles to make final image tile list
-other pixel/sizing/related vars set here
-frame destroyed and replaced
"""
def imgInit(fN, a): 
    #INPUT GLOBALS
    global tileNum #used for many funct loops
    global mainCy #frequency
    global eP
    global mPopP, mTileP
    global GenSize, ConstGenSize
    global curr
    
    curr=1

    tileNum=int(tileNumE.get()) #rows, cols set pretty proportionally. 20,25,32,49. 100, 156, 210, 400
    #cycles=1000 #T.TD: only for now. later will end when user wants, when not getting much better, etc.
    GenSize=int(GenSizeE.get()) #Pops
    eP=float(ePE.get()) #elites: 20% of top move on
    mPopP=float(mPopPE.get()) #.5=50% of pops affected (out of the ones that make it past selection, bc this happens before crossover) (can include repeated ones due to randomness)
    mTileP=float(mTilePE.get()) #.1=10% of tiles affected (can include repeated ones due to randomness)

    mainCy=int(mainCyE.get())

    ConstGenSize=copy.deepcopy(GenSize) #avoid data share -> deep copy from GenSize 
    
    global adv
    if a: #if the option was for single (basic) or multi (adv)
        adv=True
    else:
        adv=False
    
    global tiles

    #fN=takes filename, so, will take disk version (not the resized version from main)
    #can always use paint and resize it in disk, or at least a copy -> now, when "join," won't display massive one
    #or, since join returns an img, can just resize that one
    tiles=image_slicer.slice(fN, tileNum, save=False)
    #tiles=image_slicer.slice(fN, 20, 5, 4, False) #see documentation
    tiles=image_slicer.save_tiles(tiles, prefix="tile", directory=os.getcwd(),format="png") #no prefix for now=imgs get replaced. + current dir. not sure how to do new folder.
    #Note: column=row, row=column. careful w/ using those properties
    tileNum=len(tiles) #the real # used is rounded up to be proportionate, so good to change now

    global tilePixelX, tilePixelY #used in fitness funct
    global tilePixelTotal #used in fitness funct
    global popRows, popColumns #used in crossover bc going row by row, changing all columns in certain rows
    
    tilePixelX=(tiles[1].coords)[0] #gives x distance from 0 to 1 (or, that's at least how I've used it)
    popRows=(tiles[tileNum-1].position)[1]
    """ 
    goal: determine # of y pixels in a tile (bc that varies when # of tiles change)
    for x=super easy, bc tile[1] is always the next one x-wise
    for y=not as simple. as I'm going thru tiles, need to detect when y coords change the first time -> that's the answer
    """
    x=0
    while True:
        if (tiles[x].coords)[1] != 0: #once it changes
            tilePixelY=(tiles[x].coords)[1]
            popColumns=(tiles[x-1].position)[0] #guy before=in last column
            break
        x+=1
    
    tilePixelTotal=tilePixelX*tilePixelY 
    print(tilePixelTotal*tileNum)
    
    global Frame
    Frame.destroy() #=/= make var disappear
    Frame=LabelFrame(root) #comparable to initial one that was in main scope
    Frame.pack() 

    randomT() #set up first gen and s2 displays


""" INITIAL GUI/TK SET-UP """
ParOptionsL = HTMLLabel(Frame, html="<h3 style='text-align: center'>Please Enter Desired Parameters</h3>")
ParOptionsL.grid(row = 0, column = 0, columnspan=6)
ParOptionsL.fit_height()

#num of tiles
tileNumL = Label(Frame, text="Number of Tiles") #per Population")
tileNumL.grid(row = 1, column = 0)

tileNumE = Entry(Frame) #width=8)
tileNumE.grid(row=2, column=0)
tileNumE.insert(0, "25")

#update frequency
mainCyL = Label(Frame, text="Update Frequency") # (in Generations)")
mainCyL.grid(row = 1, column = 1)

mainCyE = Entry(Frame) #width=8)
mainCyE.grid(row=2, column=1)
mainCyE.insert(0, "150")

#num of pops per gen
GenSizeL = Label(Frame, text="Size of Generation") # (in Populations)")
GenSizeL.grid(row = 1, column = 2)

GenSizeE = Entry(Frame) #width=8)
GenSizeE.grid(row=2, column=2)
GenSizeE.insert(0, "4")

#elite percentage (pops per gen)
ePL = Label(Frame, text="Elite Decimal")
ePL.grid(row = 1, column = 3)

ePE = Entry(Frame) #width=8)
ePE.grid(row=2, column=3)
ePE.insert(0, ".3")

#mutation: pops affected
mPopPL = Label(Frame, text="Mutation: Populations Affected")
mPopPL.grid(row = 1, column = 4)

mPopPE = Entry(Frame) #width=8)
mPopPE.grid(row=2, column=4)
mPopPE.insert(0, ".3")

#mutation: tiles affected per pop
mTilePL = Label(Frame, text="Mutation: Tiles Affected")
mTilePL.grid(row = 1, column = 5)

mTilePE = Entry(Frame) #width=8)
mTilePE.grid(row=2, column=5)
mTilePE.insert(0, ".05")


OptionRunL=HTMLLabel(Frame, html="<h3 style='text-align: center'>Please Select an Option to Run</h3>") 
OptionRunL.grid(row=3, column=0, columnspan=6)
OptionRunL.fit_height()

MonaLisaFN="MonaLisa.png" #fileName
MonaLisa=Image.open(MonaLisaFN) #img object
#MonaLisa=ImageOps.fit(MonaLisa,(300,200)) #600,400 #150,100
#MonaLisa=ImageOps.scale(MonaLisa, 0.5)
MonaLisaTK=ImageTk.PhotoImage(MonaLisa) #TK img object
MonaLisaLabel=Label(Frame, image=MonaLisaTK)
MonaLisaLabel.grid(row=4,column=0, columnspan=2) #.pack()
MonaLisaButton=Button(Frame, text="Single Image", command=lambda: imgInit(MonaLisaFN, False), bg="black", fg="white")
MonaLisaButton.grid(row=5,column=0, columnspan=2)

#FinalDog
FinalDogFN="adv/final.png" #"Garfield1.png"
FinalDog=Image.open(FinalDogFN)
#print(FinalDog)
#FinalDog=ImageOps.fit(FinalDog,(250,300))
#FinalDog=ImageOps.scale(FinalDog, 0.5)
FinalDogTK=ImageTk.PhotoImage(FinalDog) #for TK
FinalDogLabel=Label(Frame, image=FinalDogTK)
FinalDogLabel.grid(row=4, column=2, columnspan=2) #.pack() #pack what's in label now #Q: if reusing vars doesn't hurt, how to remove items?
FinalDogSingleButton=Button(Frame, text="Single Image", command=lambda: imgInit(FinalDogFN, False), bg="black", fg="white")
FinalDogSingleButton.grid(row=5, column=2)
FinalDogMultiButton=Button(Frame, text="Multi-Image", command=lambda: imgInit(FinalDogFN, True), bg="black", fg="white")
FinalDogMultiButton.grid(row=5, column=3)

#Sandler
SandlerFN="sandler.png" 
Sandler=Image.open(SandlerFN)
#Sandler=ImageOps.fit(Sandler,(300,200))
#Sandler=ImageOps.scale(Sandler, 0.5)
SandlerTK=ImageTk.PhotoImage(Sandler) #for TK
SandlerLabel=Label(Frame, image=SandlerTK)
SandlerLabel.grid(row=4, column=4, columnspan=2) #.pack() #pack what's in label now #Q: if reusing vars doesn't hurt, how to remove items?
SandlerButton=Button(Frame, text="Single Image", command=lambda: imgInit(SandlerFN, False), bg="black", fg="white")
SandlerButton.grid(row=5, column=4, columnspan=2)

""" 
-stores all disk tiles into imgList (for easily picking random ones to put in first gen)
ADV- =/= use imgs from disk from slicer (those are from the final), so the file names are different here

then builds first generation
-fills list of random imgs
-replaces tile imgs w/ those
-joins into one img
"""
def randomT():
   
    """ only need to run once """
    #1. list w/ all tile names + images from disk
    ImgFNList=[]
    global imgList #will use in mutation. basic=has "final" img tiles (bc same as initial), adv=has initial pics (diff from final)
    imgList=[] #! image objects of all tiles -> for basic, will be final img tiles -> will compare random tiles to these
    
    if(adv):
        for x in range(40,51): #60,66 #40,51
            curr="adv/"+str(x)+".png"
            currImg=Image.open(curr) #less costly than replacing in list
            #imgList.append(currImg)
            imgList.append(ImageOps.fit(currImg,(tilePixelX, tilePixelY))) #now =/= need to update in paint every time diff tile # or diff pics
            ImgFNList.append(curr) #no use for now. only care for img objects for pixels
    else: #need rows and cols bc of slicer disk naming process
        for x in range(1,popRows+1): #1,5=01-04 here for ROW
            if x<10:
                currX="tile_0"+str(x)+"_"
            else:
                currX="tile_"+str(x)+"_"
            
            for y in range(1,popColumns+1): #1,6=01-05 on inner loop for COL
                if y<10:
                    curr=currX+"0"+str(y)+".png"
                else:
                    curr=currX+str(y)+".png"

                currImg=Image.open(curr) #less costly than replacing in list
                imgList.append(currImg)
                #imgList.append(ImageOps.fit(currImg,(55,50)))
                ImgFNList.append(curr) #no use for now. only care for img objects for pixels
        
    
    global tilesList #might be using in diff functs
    tilesList=copy.deepcopy(tiles) #not shallow
    tilesList=list(tilesList) #tuple->list of tuples.data type can change, sure?
    tilesList=L(tilesList) #now can store fitness

    global Gen
    Gen=[] #current generation. possibly =/= need diff final list

    
    """ 
    SETS UP INITIAL GENERATION!
    run multiple times 
    will run through all pops
    append each pop (w/ fitness) to Gen
    """
    for pop in range(0,ConstGenSize): 
        
        #2. loop to generate random # + make a list of random images (used to also display tiles)
        #col=0
        #cnt=0 
        RimgList=[] #! random img objects -> 1 population img. later: list w/in list for allowing multiple population imgs? or dict? or?
        RFNList=[]
        #TKList=[] #random TK img objects
        #TKlabelList=[] #random TK img labels
        #txtlabelList=[]
        size=len(imgList)
        for x in range(0,tileNum): #if 20 -> 0-19 iterations
            num=random.randint(0, size-1) #now good for both basic+adv bc allows for a shorter set of imgs than tiles (you just run the random on those more times than the #of imgs)
            #tileNum-1) #random # between 0-19 -> yes bc list 
            RimgList.append(imgList[num])
            RFNList.append(ImgFNList[num])

        #3. replace all the tile imgs w/ random ones
        for x in range(0,tileNum): 
            tilesList[x].image = RimgList[x] 
            tilesList[x].filename = RFNList[x] 

        Gen.append(copy.deepcopy(tilesList)) #brings fitness along 
        #tilesList just has current pop -> temp var, bc L
    
    screen2()
  
""" 
SCREEN2
sets up second screen
all the labels, buttons, and initial process images
"""
def screen2():

    global joinTKList, joinTKlabelList #bc will be calling main() from a button after this
    joinTKList=[] #population TK img objects=generation
    joinTKlabelList=[] #population TK img labels=generation

    startL = Label(Frame, text = 'Start', font=10)
    startL.grid(row = 0, column = 0)
    currentL = Label(Frame, text = 'Current', font=10)
    currentL.grid(row = 0, column = 1, columnspan=2)
    endL = Label(Frame, text = 'End', font=10)
    endL.grid(row = 0, column = 3)

    #INITIAL
    joinImg=image_slicer.join(Gen[0]) 
    joinTKList.append(ImageTk.PhotoImage(joinImg))
    joinTKlabelList.append(Label(Frame, image=joinTKList[0]))
    joinTKlabelList[0].grid(row=1, column=0)

    #CURRENT
    joinImg=image_slicer.join(Gen[0]) 
    joinTKList.append(ImageTk.PhotoImage(joinImg))
    joinTKlabelList.append(Label(Frame, image=joinTKList[1]))
    joinTKlabelList[1].grid(row=1, column=1, columnspan=2)

    #FINAL
    joinImg=image_slicer.join(tiles) 
    #print(joinImg.size)
    joinTKList.append(ImageTk.PhotoImage(joinImg))
    joinTKlabelList.append(Label(Frame, image=joinTKList[2]))
    joinTKlabelList[2].grid(row=1, column=3)

    global GoButton
    GoButton = Button(Frame, text="GO. Keep going!", command=main, bg="black", fg="white") #KEEP ER MOVIN
    GoButton.grid(row = 2, column = 1)

    DoneButton = Button(Frame, text="DONE. Looks Good!", command=done, bg="black", fg="white")
    DoneButton.grid(row = 2, column = 2)

    """ DoneButton = Button(Frame, text="click", command=click)
    DoneButton.grid(row = 2, column = 1) """

def done():
    exit()

""" def click():
    for x in range(0,cycles):
        GoButton.invoke()
        time.sleep(1) """

""" 
MAIN
#Cycle
Each funct will run # of pop times
each have their own loops for this
here, only concerned w/ how many generations/cycles we want to run this process

then updates display! -> MAIN runs as many times as you click the button "GO"
"""
def main():
    global Gen #bc of shuffle, sort
    global joinTKList, joinTKlabelList #for new label space
    global curr

    for cycle in range(0,mainCy): 
        #print(Gen) #should have 4 Ls (lists w/ fitnesses)
        fitness2()
        print("GENERATION #",curr,": ",sep="") 
        for x in range(0,GenSize):
            print(Gen[x].fitness)
        selection()
        mutation()
        crossover()
        random.shuffle(Gen)

        curr+=1
    
    Gen.sort(key=sortFitness, reverse=True)

    #UPDATE DISPLAY (w/ best one as current)
    joinImg=image_slicer.join(Gen[GenSize-1]) #best
    joinTKList.append(ImageTk.PhotoImage(joinImg))
    temp=len(joinTKList)-1 #new size of list. eff bc using twice
    joinTKlabelList.append(Label(Frame, image=joinTKList[temp])) #all to keep clean vars
    joinTKlabelList[temp].grid(row=1, column=1, columnspan=2) #replace. may destroy before, too.

    random.shuffle(Gen)

""" 
CROSSOVER
-pick random pop in Gen 
-deep copy append into Gen (new spot)
-pick another random pop in Gen 
-every other row, switch each tile w/ 2nd pop
=now a mix of both parents, each getting every other row

repeat till amt recovered

update GenSize (so, set as global... again)

"""
def crossover():
    global GenSize
    
    #stop if reached original amt? (need original var)
	#could be good bc what if elitism is set low? need more -> keeps going
    while GenSize != ConstGenSize: 

        p1=random.randint(0,GenSize-1)
        p2=random.randint(0,GenSize-1)

        tileCnt=0;
        Gen.append(copy.deepcopy(Gen[p1])) 
        change=False #first row can stay. next row, I want it to change, grabbing tiles from other parent
        
        GenSize=len(Gen) #=/= sure how many by end. but if stop pt works, can just set back to original var (TBD)
        for r in range(0,popRows): #rows
            
            if(change):  #changing. pulling from other parent
                #$: store Gen[p2] in a var to not continue to access list
                for c in range(0,popColumns): #cols
                    Gen[GenSize-1][tileCnt].image=Gen[p2][tileCnt].image #swapping row, tile by tile
                    tileCnt+=1
            else: #no tile swapping here
                #do need current tileCnt for when I change. this=more efficient than looping
                tileCnt+=popColumns
            change= not change #toggle

    
    
"""  
MUTATION
need:
-mPopP (currently half)
-Gen -Gen length -random loop
-pick a random pop
-change some random tiles to some random tiles (from original, global tiles?)
"""
def mutation():
    size=len(imgList) #efficiency
    mPopAmt=round(GenSize*mPopP) 
    mTileAmt=round(mTileP*tileNum)
    for x in range(0, mPopAmt):
        #$: can return right in [] w/o variables. currently like this for readability
        pop=random.randint(0, GenSize-1) #0-3. which pop?
        for y in range(0, mTileAmt):
            tile=random.randint(0, tileNum-1) 
            tileO=random.randint(0, size-1) 
            Gen[pop][tile].image = imgList[tileO] #tiles[tileO].image
            #basic/adv=imgList has all initial imgs. diff for each, but can use same var!

    #print("GENERATION #1 AFTER MUTATION: ")
    """ print(GenSize)
    for x in range(0,3):
        print(Gen[x].fitness) """



#used to sort messy Ls (that includes fitness property) by fitness!
def sortFitness(e):
    return e.fitness #yes!!!

"""  
ELITES: sort, get top %, throw into generation
BRACKET SELECTION: go 2x2, picking best, throwing into generation
slight variance if even or odd size of gen 
Note: bracket selects from ALL. thus, possible it picks out some elites again, which is fine.
GenSize: Updated here!
"""
def selection():
    E=copy.deepcopy(Gen)
    S=copy.deepcopy(Gen) 
    Gen.clear() #now =/= need Gen, bc all items in copy lists

    E.sort(key=sortFitness, reverse=True)
    size=len(E)
    Esize=round(size*eP) 

    #ELITES 
    for x in range(size-Esize,size): 
        Gen.append(copy.deepcopy(E[x]))

    #BRACKET SELECTION
    if(size % 2 == 0): #even. can do 2x2 whole way. 

        for x in range(0,size,2): #size depends on # of pops
            if S[x].fitness < S[x+1].fitness: #throw best in there
                Gen.append(copy.deepcopy(S[x]))
            else:
                Gen.append(copy.deepcopy(S[x+1]))
                
    else: #odd. 2x2 until last guy, which is just thrown in, probs
        for x in range(0,size-1,2): #size depends on # of pops
            if S[x].fitness < S[x+1].fitness: #throw best in there
                Gen.append(copy.deepcopy(S[x]))
            else:
                Gen.append(copy.deepcopy(S[x+1]))
        Gen.append(copy.deepcopy(S[size-1])) #last guy
    
    global GenSize #for this nested funct to affect the var for main... maybe if it wasn't nested, it'd work
    GenSize=len(Gen) #changed. now diff from size, Esize



"""
yxy PACKET PIXEL BY PIXEL
5 yxy packets. top, left, mid, right, bottom
much faster than all pixels, but still catching much more detail difference than avg color of tile

the smaller the # -> the smaller the difference -> the closest in match

=/=pre-compute
so, need tiles of initial/original (global tiles) + tiles of manipulated/current (tilesList)
+ pixel arrays for all tiles of each (each loop pass=1 tile of each)
"""
def fitness2(): 
    
    for pop in range(0,GenSize): 
        #cnt=0
        #sum=0
        imgSum=0
        for t in range(0,tileNum): 
            #CURRENT TILE
            pixO=tiles[t].image.load() #original
            pixC=Gen[pop][t].image.load() #current

            """ #each tile=5 packets of 10x10 -> 500. each 1 a nested loop after finding start pt """
            for packet in range(0,5):
                
                if packet==0: #TOP
                    startX=round((tilePixelX/2)-ps1)
                    endX=startX+ps2
                    startY=0
                    endY=startY+ps2
                elif packet==1: #MID
                    startX=round((tilePixelX/2)-ps1)
                    endX=startX+ps2
                    startY=round((tilePixelY/2)-ps1)
                    endY=startY+ps2
                elif packet==2: #BOTTOM
                    startX=round((tilePixelX/2)-ps1)
                    endX=startX+ps2
                    startY=tilePixelY-ps2
                    endY=startY+ps2
                elif packet==3: #LEFT
                    startX=0
                    endX=ps2
                    startY=round((tilePixelY/2)-ps1)
                    endY=startY+ps2
                else: #RIGHT
                    startX=tilePixelX-ps2
                    endX=startX+ps2
                    startY=round((tilePixelY/2)-ps1)
                    endY=startY+ps2
                
                
                for x in range(startX, endX):
                    
                    for y in range(startY, endY):
                        colorO=pixO[x,y] #current RGB tuple from original
                        colorC=pixC[x,y] #current RGB tuple from changed/current
                        #cnt+=1
                        pixDiff=0
                        for color in range(3): #color=R, G, B. goes to each to compare separately
                            pixDiff+=abs(colorO[color]-colorC[color]) #add differences up
                        imgSum+=pixDiff
                

        avg=imgSum/tileNum #sum can work, too, but avg will be a more manageable number (smaller)
        Gen[pop].fitness=avg 



""" AVERAGE TILE COLOR
*not currently usable. assumes certain small additions*

requires pre-comp (could be a funct, but called before cycle)
+ need global tiles to be L as well? bc each tile needs an tileAvgColor

assume that for now
add up all Rs, Gs, and Bs? compare to all Rs, Gs, and Bs from final?
or wait, how to do avg tile color??? is that it?
can also divide each by # of pix

will probs be very diff if I get the precomp to help w/ current ones too
"""
def fitnessAvg():
    for pop in range(0,GenSize): 
        
        for x in range(0,tileNum): 
            #CURRENT TILE
            AvgO=tiles[x].tileAvgColor #original #TD: assuming this exists
            pixC=Gen[pop][x].image.load() #current
            sum=[0,0,0] #RGB sum for each tile. will compare w/ AvgO at end of this loop

            #each tile=50x75, bc 250x300 -> 3750. TD: can put var later 
            for r in range(0,tilePixelX):
                
                for c in range(0,tilePixelY):
                    #colorO=pixO[r,c] #current RGB tuple from original
                    colorC=pixC[r,c] #current RGB tuple from changed/current

                    for color in range(3): #color=R, G, B. goes to each to compare separately
                        sum[color]+=colorC[color] #

            sum[0]/=tilePixelTotal #avg=sum/# of pixels
            sum[1]/=tilePixelTotal
            sum[2]/=tilePixelTotal
            #now compare, do smth (wait, do I compare? or just send #?), and reset


            #print(equalCnt) #how many pixels correct in this tile? should be 60x50=3,000
            #prints 20x4=80 times, so not awful
            #but can also have an if asking if it's the right # -> same -> many less prints
        
        avg=sum/tileNum #sum can work, too, but avg will be a more manageable number (smaller)
        Gen[pop].fitness=avg 
        #print("fitness score of population img #",pop+1,sep="") #sep by , . sep= indicates diff par
        #print(Gen[pop].fitness) #T
    #return avg


""" PIXEL BY PIXEL
=/=pre-compute
so, need tiles of initial/original (global tiles) + tiles of manipulated/current (tilesList)
+ pixel arrays for all tiles of each (each loop pass=1 tile of each)

calcs sum via all correct pixels of all tiles -> avg for whole img per tile -> returned
"""
def fitness(): 
    for pop in range(0,GenSize): 
        
        sum=0
        for x in range(0,tileNum): 
            #CURRENT TILE
            pixO=tiles[x].image.load() #original
            pixC=Gen[pop][x].image.load() #current
            equalCnt=0

            #each tile=50x75, bc 250x300 -> 3750. 
            for r in range(0,tilePixelX):
                
                for c in range(0,tilePixelY):
                    colorO=pixO[r,c] #current RGB tuple from original
                    colorC=pixC[r,c] #current RGB tuple from changed/current

                    equal=True #assume RGBs are equal
                    for color in range(3): #color=R, G, B. goes to each to compare separately
                        if colorO[color]!=colorC[color]: #e- are the R's equal?
                            equal=False 
                            break #if R differs=no match=why bother to keep going
                    if equal:
                        equalCnt+=1
            sum+=equalCnt #add correct pixels

            #print(equalCnt) #how many pixels correct in this tile? should be 60x50=3,000
            #prints 20x4=80 times, so not awful
            #but can also have an if asking if it's the right # -> same -> many less prints
        
        avg=sum/tileNum #sum can work, too, but avg will be a more manageable number (smaller)
        Gen[pop].fitness=avg 
        #print("fitness score of population img #",pop+1,sep="") #sep by ,. sep= indicates diff par
        #print(Gen[pop].fitness) #T
    #return avg
    

root.mainloop() #root fine? bc window. or does this only run @start to run window?

