Files
Seminar1/classes.py
T
2026-05-10 05:02:33 +02:00

499 lines
15 KiB
Python

import typing
import builtins
import math
from enum import Enum
from math import fabs
from PIL import Image, ImageFilter
import os
from typing import NamedTuple, List
DIRECTORY = os.path.dirname(os.path.abspath(__file__))+"/"
PLOT_EMPTY = Image.open(DIRECTORY+"sprites/emptySprite.png").convert("RGBA")
PLOT_GARAGE = Image.open(DIRECTORY+"sprites/Park.png").convert("RGBA")
PLOT_CAR = Image.open(DIRECTORY+"sprites/car.png").convert("RGBA")
PLOT_GARAGE_CAR = Image.open(DIRECTORY+"sprites/CarOnPark.png").convert("RGBA")
OUTPUT_DIR = DIRECTORY + "output/"
FULL_GARAGE_COLOR = (1,0.5,0.5,1)
full_garage_sprite = PLOT_GARAGE.copy()
r,g,b,a = full_garage_sprite.split()
r = r.point(lambda i: i * FULL_GARAGE_COLOR[0])
g = g.point(lambda i: i * FULL_GARAGE_COLOR[1])
b = b.point(lambda i: i * FULL_GARAGE_COLOR[2])
a = a.point(lambda i: i * FULL_GARAGE_COLOR[3])
full_garage_sprite = Image.merge("RGBA",(r,g,b,a))
class RenderTaskType(Enum):
LINE = 1 # data [x,y,x2,y2,color(r,g,b,a)]
class RenderTask():
def __init__(self, type: RenderTaskType, name:str , data :list):
self.type = type
self.name = name
self.data = data
class RenderTaskFrame:
def __init__(self,tasks:List[RenderTask],become_permanent:bool = False, become_temp:bool = False):
self.tasks:List[RenderTask] = tasks
self.become_permanent:bool = become_permanent
self.become_temp:bool = become_temp
class RenderTaskStack:
_Tasks:List[RenderTask] = []
_TempStack:List[RenderTask] = []
_SingleFrameRenderTaskList: List[RenderTaskFrame] = []
@staticmethod
def has_frame():
return len(RenderTaskStack._SingleFrameRenderTaskList) >0
@staticmethod
def clear_all():
RenderTaskStack._Tasks = []
RenderTaskStack._TempStack = []
@staticmethod
def add_temp(task:RenderTask):
RenderTaskStack._TempStack.append(task)
@staticmethod
def add_temps(tasks:List[RenderTask]):
RenderTaskStack._TempStack += tasks
@staticmethod
def clear_temp():
RenderTaskStack._TempStack = []
@staticmethod
def add_permanent( task: RenderTask):
RenderTaskStack._Tasks.append(task)
@staticmethod
def add_permanents(task: List[RenderTask]):
RenderTaskStack._Tasks+=task
@staticmethod
def add_single_frame_render(frame: RenderTaskFrame):
RenderTaskStack._SingleFrameRenderTaskList.append(frame)
@staticmethod
def pop_single_frame_render()->RenderTaskFrame:
return RenderTaskStack._SingleFrameRenderTaskList.pop(0)
@staticmethod
def get_permanents():
return RenderTaskStack._Tasks.copy()
@staticmethod
def get_temps():
return RenderTaskStack._TempStack.copy()
# füge alle kanten in eine set hinzu, sorte dis liste bei distance und pick die kürzesten die nen neuen knoten hinzufügen
class Linedrawer():
@staticmethod
def plotLineLow(x0, y0, x1, y1):
dx = x1 - x0
dy = y1 - y0
yi = 1
if dy < 0:
yi = -1
dy = -dy
D = (2 * dy) - dx
y = y0
points = []
for x in range( x0,x1):
points.append((x, y+1))
if D > 0:
y = y + yi
D = D + (2 * (dy - dx))
else:
D = D + 2*dy
return points
@staticmethod
def plotLineHigh(x0, y0, x1, y1):
dx = x1 - x0
dy = y1 - y0
xi = 1
if dx < 0:
xi = -1
dx = -dx
D = (2 * dx) - dy
x = x0
points = []
for y in range(y0, y1):
points.append((x, y+1))
if D > 0:
x = x + xi
D = D + (2 * (dx - dy))
else:
D = D + 2*dx
return points
@staticmethod
## bresenhams line algo #########
def plotLine(x0, y0, x1, y1):
if abs(y1 - y0) < abs(x1 - x0):
if x0 > x1:
return Linedrawer.plotLineLow(x1, y1, x0, y0)
else:
return Linedrawer.plotLineLow(x0, y0, x1, y1)
else:
if y0 > y1:
return Linedrawer.plotLineHigh(x1, y1, x0, y0)
else:
return Linedrawer.plotLineHigh(x0, y0, x1, y1)
#################
@staticmethod
def draw_line_between_points(img:Image, x1,y1,x2,y2,color = (255,255,255))-> Image:
if(x1==x2 and y1 == y2):
x1+=10
x2-=10
pixels = img.load() # create the pixel map
print((x1,y1,x2,y2))
points = Linedrawer.plotLine(x1,y1,x2,y2)
for pt in points:
if pt[0] >= 0 and pt[0] <img.size[0] and pt[1] >= 0 and pt[1] <img.size[1]:
pixels[pt[0],pt[1]] = color
pixels[pt[0],pt[1]-1] = color
pixels[pt[0],pt[1]+1] = color
pixels[pt[0]+1,pt[1]] = color
pixels[pt[0]+1,pt[1]] = color
return img
class Point():
def __init__(self,x,y):
self.x = x
self.y = y
def get_location(self):
return Point(self.x,self.y)
def __eq__(self, value):
if not isinstance(value,Point): return False
return value.x == self.x and value.y == self.y
def __hash__(self):
return hash((self.x, self.y))
class Car(Point):
"""A class which represents a car"""
def __init__(self, p:Point, timestamp:int,walk = 0):
super().__init__(p.x, p.y)
self.timestamp:int = timestamp
self.walk = walk
def get_location(self):
return super().get_location()
def get_walk(self):
return self.walk
def set_walk(self,value):
self.walk = value
def add_to_walk(self,value):
self.walk += value
def __eq__(self, value):
if not isinstance(value,Car): return False
return value.x == self.x and value.y == self.y and self.timestamp == value.timestamp
def __hash__(self):
return hash((self.x, self.y,self.timestamp))
class Garage(Point):
"""A class which represents a garage"""
def __init__(self, p:Point, max_capacity):
super().__init__(p.x, p.y)
self.max_capacity = max_capacity
self.cars_parked = []
def get_location(self):
return super().get_location()
def __eq__(self, value):
if not isinstance(value,Garage): return False
return value.x == self.x and value.y == self.y and self.max_capacity == value.max_capacity
def __hash__(self):
return hash((self.x, self.y,self.max_capacity,1))# hash with 1 to avoid collisions witch car class
class DistanceFunction():
def apply(self, pointA: Point, pointB: Point):
return math.sqrt( ( (pointA.x-pointB.x) **2) + ( (pointA.y-pointB.y) **2) )
class MetricSpace:
@staticmethod
def getPopulatedSpace(x,y):
points = set()
for i in range(x):
for j in range(y):
points.add(Point(i,j))
return MetricSpace(points,DistanceFunction())
def __init__(self,points:set, distancefunction:DistanceFunction):
self.points: set = points
self.distancefunction:DistanceFunction = distancefunction
def get_max_min_values(self)->list:
if(len(self.points)==0): return None
point = self.points.pop()
self.points.add(point)
x_max = point.x
y_max = point.y
x_min = x_max
y_min = y_max
for p in self.points:
if p.x > x_max: x_max = p.x
if p.y > y_max: y_max = p.y
if p.x < x_min: x_min = p.x
if p.y < y_min: y_min = p.y
x_dif = x_max-x_min
y_dif = y_max-y_min
return [x_max,y_max,x_min,y_min, x_dif +1,y_dif+1]
class GarageManager:
def __init__(self, garages:set, capacities:dict ={}):
self.garages:set[Garage] = garages
# To delete
self.capacities = capacities
self.garageArrays = []
self.currentFillLevel = capacities.copy()
# initialize capacities
for x in self.currentFillLevel.keys():
self.currentFillLevel[x]=0
def get_garage_at_location(self, location:Point):
for x in self.garages:
if x.get_location() == location.get_location():
return (True,x)
return (False, Point(-100,-100))
def is_full(self, garage:Point):
garage_found = False
current_garage:Garage
for g in self.garages:
if g.x == garage.x and g.y == garage.y:
current_garage = g
garage_found = True
break
if not garage_found:
raise RuntimeError(" garage not found in is Full check")
return current_garage.max_capacity <= len(current_garage.cars_parked)
def getCurrentCapacity(self, garage:Point):
"""DEPRECATED"""
raise DeprecationWarning()
return self.currentFillLevel.get(garage,-99)
def getMaxCapacity(self, garage:Point):
raise DeprecationWarning()
return self.capacities.get(garage,-99)
def addToCapacity(self,garage:Point,amount):
raise DeprecationWarning()
self.currentFillLevel[garage] = self.getCurrentCapacity(garage) + amount
class frameGenerator():
def __init__(self, space:MetricSpace, garages:GarageManager):
self.space = space
self.garages:GarageManager = garages
self.garagePoints = []
for g in garages.garages:
self.garagePoints.append(g.get_location())
self.car_exists :bool = False
self.car:Point = None
self.space_limits = space.get_max_min_values()
"[ x_max, y_max, x_min, y_min, x_dif, y_dif]"
self.frame_width = (self.space_limits[4]*34)-2 # plot +2 pixel for road -2 for end
self.frame_height = (self.space_limits[5]*34)-2 # plot +2 pixel for road -2 for end
self.time = 0
self.frameNum = 0
def spawnCar(self,point:Point):
if(not self.car_exists):
self.frameNum = 0
self.time += 1
self.car = point
self.car_exists= True
self.renderFrame()
else:
print("ERROR: double cars!!!!")
raise IndexError
def add_transition(self, point_B:Point):
if(self.car_exists):
x0,y0 =self._point_to_pixel_center(self.car)
x1,y1 =self._point_to_pixel_center(point_B)
rend_task = RenderTask(RenderTaskType.LINE, "transition",[x0,y0,x1,y1, (255,100,100,200)])
RenderTaskStack.add_single_frame_render(RenderTaskFrame( [rend_task]))
self.renderFrame()
self.car = point_B
self.renderFrame()
self.car = None
self.car_exists= False
else:
print("ERROR: no cars was driven somewhere!!!!")
raise IndexError
def _point_to_pixel(self, point:Point)->tuple:
x = point.x-self.space_limits[2]#xmin
y = point.y-self.space_limits[3]#ymin
pix_x = x*34
pix_y = y*34
return (pix_x,pix_y)
@staticmethod
def point_to_pixel_center(space, point: Point) -> tuple:
space_limits = space.get_max_min_values()
x = point.x - space_limits[2] # xmin
y = point.y - space_limits[3] # ymin
pix_x = x * 34 + 16
pix_y = y * 34 + 16
return (pix_x, pix_y)
def _point_to_pixel_center(self, point:Point)->tuple:
x = point.x-self.space_limits[2]#xmin
y = point.y-self.space_limits[3]#ymin
pix_x = x*34 +16
pix_y = y*34 +16
return (pix_x,pix_y)
def _insert_Image(self, frame , pix_x,pix_y, image:Image):
img:Image.core.PixelAccess = image.load()
fme:Image.core.PixelAccess = frame.load()
width, height = image.size
for x in range(width):
for y in range(height):
if img[x,y] != (0,0,0,0):
fme[pix_x+x,pix_y+y] = img[x,y]
def renderFrame(self, drawLine = False, LineColor = (200,0,0,200), endPoint:Point = None ):
frame = Image.new("RGBA",(self.frame_width,self.frame_height),(100,100,100,255))
for p in self.space.points:
x,y = self._point_to_pixel(p)
img = PLOT_EMPTY
if((self.car_exists) and (p.get_location() == self.car.get_location()) and (p.get_location() in self.garagePoints)):
img = PLOT_GARAGE_CAR
elif(self.car_exists and p.get_location() == self.car.get_location()):
img = PLOT_CAR
elif(p.get_location() in self.garagePoints):
if self.garages.is_full(p):
img = full_garage_sprite
else:
img = PLOT_GARAGE
self._insert_Image(frame, x, y,img)
# render temp and permanent
static_overlay = Image.new("RGBA", (self.frame_width, self.frame_height), (0, 0, 0, 0))
for ta in RenderTaskStack.get_permanents():
if ta.type == RenderTaskType.LINE:
car_x, car_y = ta.data[0], ta.data[1]
ep_x, ep_y = ta.data[2], ta.data[3]
Linedrawer.draw_line_between_points(static_overlay, car_x, car_y, ep_x, ep_y, ta.data[4])
for ta in RenderTaskStack.get_temps():
if ta.type == RenderTaskType.LINE:
car_x, car_y = ta.data[0], ta.data[1]
ep_x, ep_y = ta.data[2], ta.data[3]
Linedrawer.draw_line_between_points(static_overlay, car_x, car_y, ep_x, ep_y, ta.data[4])
frame = Image.alpha_composite(frame, static_overlay)
# _Tasks:List[RenderTask] = []
# _TempStack:List[RenderTask] = []
# _SingleFrameRenderTaskList: List[RenderTaskFrame] = []
firstInteration:bool = True
frame_src = frame.copy()
while (firstInteration or RenderTaskStack.has_frame()):
frame = frame_src.copy()
passingOverlay = Image.new("RGBA", (self.frame_width, self.frame_height), (0, 0, 0, 0))
if RenderTaskStack.has_frame():
framedata:RenderTaskFrame = RenderTaskStack.pop_single_frame_render()
# process task per frame
for ta in framedata.tasks:
if ta.type == RenderTaskType.LINE:
car_x,car_y = ta.data[0],ta.data[1]
ep_x,ep_y = ta.data[2],ta.data[3]
Linedrawer.draw_line_between_points(passingOverlay,car_x,car_y,ep_x,ep_y, ta.data[4])
# handle migration
if framedata.become_temp:
RenderTaskStack.add_temps(framedata.tasks)
if framedata.become_permanent:
RenderTaskStack.add_permanents(framedata.tasks)
#add overlay
frame = Image.alpha_composite(frame,passingOverlay)
# save
frame.save(OUTPUT_DIR +f"frame-t{self.time:03d}-f{self.frameNum:02d}.png",format="PNG")
self.frameNum += 1
firstInteration = False