diff --git a/src/gui/boardview.py b/src/gui/boardview.py --- a/src/gui/boardview.py +++ b/src/gui/boardview.py @@ -1,45 +1,41 @@ -import tkinter as tk - +from .resizablecanvas import ResizableCanvas from go import BLACK,WHITE ## Handles and presents the game state as detected by the program. -class BoardView(tk.Canvas): +class BoardView(ResizableCanvas): def __init__(self, master=None): - self._master=master - tk.Canvas.__init__(self, master, highlightthickness=0) + super().__init__(master) + + self.configure(width=360,height=360,background="#ffcc00") - self.width=360 - self.height=360 - self.padding=18 - self.cellWidth=(self.width-2*self.padding)/18 - self.cellHeight=(self.height-2*self.padding)/18 - self.configure(width=self.width,height=self.height,background="#ffcc00") + self._padding=18 + self._cellWidth=(self._width-2*self._padding)/18 + self._cellHeight=(self._height-2*self._padding)/18 - self.drawGrid() - master.bind("", self._onResize) + self._drawGrid() def redrawState(self,gameState): self.delete("black","white") for r,row in enumerate(gameState): for c,point in enumerate(row): - self.drawStone(r,c,point) + self._drawStone(r, c, point) - def drawGrid(self): - padding=self.padding + def _drawGrid(self): + padding=self._padding for i in range(19): - self.create_line(padding,18*i+padding,self.width-padding,18*i+padding,tags="row",fill="#000000") # rows - self.create_line(18*i+padding,padding,18*i+padding,self.height-padding,tags="col",fill="#000000") # cols + self.create_line(padding,18*i+padding,self._width-padding,18*i+padding,tags="row",fill="#000000") # rows + self.create_line(18*i+padding,padding,18*i+padding,self._height-padding,tags="col",fill="#000000") # cols - self.drawStars() + self._drawStars() - def drawStars(self): + def _drawStars(self): radius=2 for r in range(3,19,6): for c in range(3,19,6): - x=c*self.cellHeight+self.padding - y=r*self.cellWidth+self.padding + x=c*self._cellHeight+self._padding + y=r*self._cellWidth+self._padding self.create_oval(x-radius,y-radius,x+radius,y+radius,tags="star",fill='#000000') ## Draws a stone at provided coordinates. @@ -49,7 +45,7 @@ class BoardView(tk.Canvas): # @param r row coordinate, [0-18], counted from top # @param c column coordinate, [0-18], counted from left # @param color color indicator, go.BLACK or go.WHITE - def drawStone(self,r,c,color): + def _drawStone(self, r, c, color): if color==BLACK: hexCode='#000000' tag="black" @@ -58,23 +54,7 @@ class BoardView(tk.Canvas): tag="white" else: return False - x=c*self.cellWidth+self.padding - y=r*self.cellHeight+self.padding - radius=self.cellWidth/2 + x=c*self._cellWidth+self._padding + y=r*self._cellHeight+self._padding + radius=self._cellWidth/2 self.create_oval(x-radius,y-radius,x+radius,y+radius,tags=tag,fill=hexCode) - - def _onResize(self, event): - wScale=float(event.width)/self.width - hScale=float(event.height)/self.height - scale=min(wScale,hScale) - - self.width*=scale - self.height*=scale - self.scale("all",0,0,scale,scale) # rescale all the objects tagged with the "all" tag - - x=(event.width-self.width)/2 - y=(event.height-self.height)/2 - - # place the window, giving it an explicit size - self.place(in_=self._master, x=x, y=y, - width=self.width, height=self.height) diff --git a/src/gui/imgview.py b/src/gui/imgview.py new file mode 100644 --- /dev/null +++ b/src/gui/imgview.py @@ -0,0 +1,91 @@ +import logging as log + +from PIL import ImageTk + +import config +from .resizablecanvas import ResizableCanvas +from corners import Corners +from epoint import EPoint +from grid import Grid +import imageanalyzer + + +class ImgView(ResizableCanvas): + def __init__(self,master=None,parent=None): + super().__init__(master) + + self._parent=parent + self._corners=Corners() + self._boardGrid=None + + self._img=None + self._tkImg=None + self._imgSizeCoef=1 + self._imgShift=EPoint(0,0) + + self.configure(width=480,height=360) + self.bind('<1>',lambda e: self.addCorner(e.x,e.y)) + + ## Redraws the current image and its overlay. + def redraw(self): + self.delete("all") + + if self._img: + img=self._img.copy() + img.thumbnail((int(self._width),int(self._height))) + self._tkImg=ImageTk.PhotoImage(img) # just to save the image from the garbage collector + self.create_image(self._width//2, self._height//2, anchor="center", image=self._tkImg) + + for corner in self._corners.corners: + self.markPoint(corner.x,corner.y) + + if self._boardGrid!=None and config.gui.showGrid: + for r in range(19): + a=self._boardGrid.intersections[r][0] + b=self._boardGrid.intersections[r][-1] + self.create_line(a.x,a.y,b.x,b.y,fill='#00ff00') + for c in range(19): + a=self._boardGrid.intersections[0][c] + b=self._boardGrid.intersections[-1][c] + self.create_line(a.x,a.y,b.x,b.y,fill='#00ff00') + + if self._boardGrid!=None and config.gui.showBigPoints: + for r in range(19): + for c in range(19): + ((r1,c1),(r2,c2))=imageanalyzer.relevantRect(self._boardGrid.intersections[r][c], *(self._boardGrid.stoneSizeAt(r, c))) + self.create_rectangle(r1,c1,r2,c2,outline="#00ffff") + + def setImg(self,img): + w=int(self._width) + h=int(self._height) + wo,ho=img.size # o for original + widthRatio=wo/w + heightRatio=ho/h + self._imgSizeCoef=max(widthRatio,heightRatio) + # shift compensates possible horizontal or vertical empty margins from unmatching aspect ratios + self._imgShift=EPoint(wo-w*self._imgSizeCoef,ho-h*self._imgSizeCoef)/2 + + self._img=img + + ## Stores a grid corner located at x,y coordinates. + def addCorner(self,x,y): + self._corners.add(x,y) + log.debug("click on %d,%d",x,y) + log.debug("sizeCoef: %f, shift: %d,%d",self._imgSizeCoef,self._imgShift.x,self._imgShift.y) + if self._corners.canonizeOrder(): + # transform corners from show coordinates to real coordinates + log.debug(self._corners.corners) + self._boardGrid=Grid(self._corners.corners) + corners=[(c*self._imgSizeCoef+self._imgShift) for c in self._corners.corners] + self._parent.sendMsg("setCorners",(corners,)) + + self.redraw() + + ## Marks a point at the image with a green cross. Used for corners. + def markPoint(self,x,y): + self.create_line(x-3,y-3,x+4,y+4,fill="#00ff00") + self.create_line(x-3,y+3,x+4,y-4,fill="#00ff00") + + def _onResize(self,event): + super()._onResize(event) + self.redraw() diff --git a/src/gui/mainwindow.py b/src/gui/mainwindow.py --- a/src/gui/mainwindow.py +++ b/src/gui/mainwindow.py @@ -1,56 +1,27 @@ -import logging as log import tkinter as tk from tkinter import N,S,E,W -from PIL import ImageTk - -import config -import imageanalyzer -from corners import Corners -from epoint import EPoint -from grid import Grid from .boardview import BoardView +from .imgview import ImgView class MainWindow(tk.Frame): def __init__(self,parent,master=None): self.parent=parent - self.corners=Corners() - - self.currentFrame=None - self._boardGrid=None - - self.img=None - self._imgSizeCoef=1 - self._imgShift=EPoint(0,0) tk.Frame.__init__(self, master) self.grid(column=0,row=0,sticky=(N,S,E,W)) self._createWidgets() def setCurrentFrame(self,frame): - self.currentFrame=frame - - w=int(self.imgView['width']) - h=int(self.imgView['height']) - wo,ho=frame.size # o for original - widthRatio=wo/w - heightRatio=ho/h - self._imgSizeCoef=max(widthRatio,heightRatio) - # shift compensates possible horizontal or vertical empty margins from unmatching aspect ratios - self._imgShift=EPoint(wo-w*self._imgSizeCoef,ho-h*self._imgSizeCoef)/2 - - frame.thumbnail((w,h)) # resize - self.img=ImageTk.PhotoImage(frame) + self.imgView.setImg(frame) def _createWidgets(self): # a captured frame with overlay graphics - self.imgView=tk.Canvas(self) - self.imgView.configure(width=480,height=360,background="#ff00ff") + self._imgWrapper=tk.Frame(self,width=480,height=360) + self.imgView=ImgView(self._imgWrapper,self) - self.imgView.bind('<1>',lambda e: self.addCorner(e.x,e.y)) - - self.imgView.grid(column=0,row=0,sticky=(N,S,E,W)) + self._imgWrapper.grid(column=0,row=0,sticky=(N,S,E,W)) # board with detected stones self._boardWrapper=tk.Frame(self,width=360,height=360) @@ -70,54 +41,14 @@ class MainWindow(tk.Frame): self.rowconfigure(0,weight=1) # render everything - self.redrawImgView() - - ## Stores a grid corner located at x,y coordinates. - def addCorner(self,x,y): - self.corners.add(x,y) - log.debug("click on %d,%d",x,y) - log.debug("sizeCoef: %f, shift: %d,%d",self._imgSizeCoef,self._imgShift.x,self._imgShift.y) - if self.corners.canonizeOrder(): - # transform corners from show coordinates to real coordinates - log.debug(self.corners.corners) - self._boardGrid=Grid(self.corners.corners) - corners=[(c*self._imgSizeCoef+self._imgShift) for c in self.corners.corners] - self.parent.sendMsg("setCorners",(corners,)) - - self.redrawImgView() + self.imgView.redraw() ## Redraws the current image and its overlay. def redrawImgView(self): - if self.currentFrame and self.img: - self.imgView.create_image(240,180,anchor="center",image=self.img) - - for corner in self.corners.corners: - self.markPoint(corner.x,corner.y) - - if self._boardGrid!=None and config.gui.showGrid: - for r in range(19): - a=self._boardGrid.intersections[r][0] - b=self._boardGrid.intersections[r][-1] - self.imgView.create_line(a.x,a.y,b.x,b.y,fill='#00ff00') - for c in range(19): - a=self._boardGrid.intersections[0][c] - b=self._boardGrid.intersections[-1][c] - self.imgView.create_line(a.x,a.y,b.x,b.y,fill='#00ff00') - - if self._boardGrid!=None and config.gui.showBigPoints: - for r in range(19): - for c in range(19): - ((r1,c1),(r2,c2))=imageanalyzer.relevantRect(self._boardGrid.intersections[r][c], *(self._boardGrid.stoneSizeAt(r, c))) - self.imgView.create_rectangle(r1,c1,r2,c2,outline="#00ffff") - - self.imgView.grid() - - ## Marks a point at the image with a green cross. Used for corners. - def markPoint(self,x,y): - self.imgView.create_line(x-3,y-3,x+4,y+4,fill="#00ff00") - self.imgView.create_line(x-3,y+3,x+4,y-4,fill="#00ff00") - - self.imgView.grid() + self.imgView.redraw() def refreshTresholds(self,_): self.parent.sendMsg("setTresholds",tuple(),{"tresB":self.scaleTresB.get(), "tresW":self.scaleTresW.get()}) + + def sendMsg(self,actionName,args=tuple(),kwargs=None): + self.parent.sendMsg(actionName,args,kwargs) diff --git a/src/gui/resizablecanvas.py b/src/gui/resizablecanvas.py new file mode 100644 --- /dev/null +++ b/src/gui/resizablecanvas.py @@ -0,0 +1,34 @@ +import tkinter as tk + + +class ResizableCanvas(tk.Canvas): + def __init__(self, master=None): + tk.Canvas.__init__(self, master, highlightthickness=0) + + self._master=master + self._width=0 + self._height=0 + + master.bind("", self._onResize) + + def configure(self,*args,**kwargs): + if "width" in kwargs: self._width=kwargs["width"] + if "height" in kwargs: self._height=kwargs["height"] + + super().configure(*args,**kwargs) + + def _onResize(self, event): + wScale=float(event.width)/self._width + hScale=float(event.height)/self._height + scale=min(wScale,hScale) + + self._width*=scale + self._height*=scale + self.scale("all",0,0,scale,scale) # rescale all the objects tagged with the "all" tag + + x=(event.width-self._width)/2 + y=(event.height-self._height)/2 + + # place the widget, giving it an explicit size + self.place(in_=self._master, x=x, y=y, width=self._width, height=self._height) + return scale