from PIL import Image,ImageDraw,ImageFilter
import numpy as np
from pathlib import Path
from collections import deque
A=Path(__file__).parent/'deliverables/assets';A.mkdir(parents=True,exist_ok=True)
source=A/'scale410.png'
im=Image.open(source).convert('RGBA');ar=np.array(im);white=(ar[:,:,:3].min(axis=2)>247).astype('uint8')*255
mask=Image.fromarray(white).copy();ImageDraw.floodfill(mask,(0,0),128);bg=np.array(mask)==128;ar[:,:,3]=np.where(bg,0,255);device=Image.fromarray(ar);device=device.crop(device.getbbox());device.save(A/'410-original-cutout.png')
background=Image.open(A/'scene-clean-background.png').convert('RGBA');background.save(A/'scene-clean-background.png')
food=Image.open(A/'food-pan-sheet.png').convert('RGBA');fw,fh=food.size;print('device',device.size,'bg',background.size,'food',food.size)
# Extract generated pan cells; source device pixels are never repainted.
pans=[]
for j in range(8):
 cell=food.crop((j%4*fw//4,j//4*fh//2,(j%4+1)*fw//4,(j//4+1)*fh//2));w,h=cell.size
 coords=[(.20,.274),(.85,.274),(.91,.303),(.994,.697),(.948,.791),(.883,.85),(.23,.85),(.151,.81),(.069,.706),(.172,.303)]
 m=Image.new('L',(w,h));ImageDraw.Draw(m).polygon([(int(x*w),int(y*h)) for x,y in coords],fill=255);m=m.filter(ImageFilter.GaussianBlur(.65));cell.putalpha(m);cell=cell.crop(cell.getbbox());cell.save(A/f'food-pan-{j+1}.png');pans.append(cell)
scale=2;can=background.resize((background.width*scale,background.height*scale),Image.Resampling.LANCZOS)
placements=[(707,323,148),(827,348,148),(947,374,148),(1068,401,148),(561,526,162),(690,557,162),(821,591,162),(953,627,162)]
for j,(cx,base,h) in enumerate(placements):
 w=int(h*device.width/device.height);dev=device.resize((w*scale,h*scale),Image.Resampling.LANCZOS);x=int(cx-w/2);y=int(base-h);can.alpha_composite(dev,(x*scale,y*scale))
 pw=int(w*.78);ph=int(pw*pans[j].height/pans[j].width);pan=pans[j].resize((pw*scale,ph*scale),Image.Resampling.LANCZOS);can.alpha_composite(pan,((int(cx-pw/2))*scale,(base-ph-4)*scale))
# Restore foreground people from the unchanged scene plate for correct occlusion.
for poly in [[(472,430),(512,434),(536,460),(548,502),(549,544),(532,570),(527,675),(504,705),(472,694),(456,625),(452,513)],[(867,499),(900,496),(919,518),(925,548),(940,560),(939,595),(932,629),(925,740),(904,784),(872,778),(864,714),(850,630),(838,577),(842,535)]]:
 m=Image.new('L',background.size);ImageDraw.Draw(m).polygon(poly,fill=255);fg=background.copy();fg.putalpha(m);can.alpha_composite(fg.resize(can.size,Image.Resampling.LANCZOS))
can.convert('RGB').save(A/'scene-original-410-composite.jpg',quality=96)
# Insert the exact single-floor dashboard screenshot into the front-hall display.
board=Image.open(A/'nutrition-display.png').convert('RGBA')
dst=[(1296*scale,65*scale),(1361*scale,36*scale),(1358*scale,178*scale),(1293*scale,211*scale)]
src=[(0,0),(board.width,0),(board.width,board.height),(0,board.height)]
mat=[];rhs=[]
for (x,y),(u,v) in zip(dst,src):
 mat.extend([[x,y,1,0,0,0,-u*x,-u*y],[0,0,0,x,y,1,-v*x,-v*y]]);rhs.extend([u,v])
coef=np.linalg.solve(np.array(mat,float),np.array(rhs,float));warp=board.transform(can.size,Image.Transform.PERSPECTIVE,tuple(coef),Image.Resampling.BICUBIC)
mm=Image.new('L',can.size);ImageDraw.Draw(mm).polygon(dst,fill=255);warp.putalpha(mm);can.alpha_composite(warp);can.convert('RGB').save(A/'scene-original-410-composite.jpg',quality=96)
