#!/usr/bin/env python3 """Deterministic narrow post extension; original pixels untouched outside alpha.""" from pathlib import Path from PIL import Image,ImageDraw,ImageFilter import numpy as np,json,hashlib,shutil p=Path(__file__).resolve().parent.parent base=Image.open(p/'source/base-v04.png').convert('RGB');W,H=base.size # Master coordinates: begin inside the final existing segment, terminate at socket. box=(310,994,379,2652);s=4;x0,y0,x1,y1=box layer=Image.new('RGBA',((x1-x0)*s,(y1-y0)*s));d=ImageDraw.Draw(layer) def left(y):return 332.5-(y-975)*12.5/(2645-975) def pt(x,y):return ((x-x0)*s,(y-y0)*s) def poly(points,fill):d.polygon([pt(x,y) for x,y in points],fill=fill) # Dark structural silhouette overlays the cloud, with a small shaped ground end. poly([(left(994),994),(left(994)+42,994),(left(2626)+40,2626),(left(2645)+35,2645),(left(2645)+1,2640)],'#19190f') # Unequal segment heights mirror the extant near and far posts. edges=[994,1200,1417,1601,1818,2040,2259,2441,2628] for i,(a,b) in enumerate(zip(edges,edges[1:])): top=a+6 if i else a;bottom=b-7 l1,l2=left(top)+9,left(bottom)+9;wid=23 poly([(l1,top),(l1+wid,top+1),(l2+wid,bottom),(l2,bottom-1)],'#c08b28') poly([(l1+3,top),(l1+wid-3,top+1),(l2+wid-3,bottom),(l2+3,bottom-1)],'#f0ca51') poly([(l1+4,top+2),(l1+9,top+2),(l2+9,bottom-2),(l2+4,bottom-2)],'#f9e591') # Inset diagonal pane break in selected longer segments, never across the outline. if i in [2,5,7]: mid=(top+bottom)/2;lx=left(mid)+9 d.line([pt(lx,mid-10),pt(lx+23,mid+11)],fill='#8b6726',width=2*s) # Contained worked-glass variation sampled from the original source, not fresh noise. arr=np.array(layer);gold=(arr[:,:,3]>0)&(arr[:,:,0]>100) tex=base.crop((510,600,610,850)).resize(layer.size,Image.Resampling.BICUBIC) t=np.asarray(tex,dtype=float).mean(axis=2);variation=np.clip((t-t.mean())*.26,-17,17) rgb=arr[:,:,:3].astype(float);rgb[gold]+=variation[gold,None];arr[:,:,:3]=np.clip(rgb,0,255).astype('uint8') layer=Image.fromarray(arr).filter(ImageFilter.GaussianBlur(1.6)).resize((x1-x0,y1-y0),Image.Resampling.LANCZOS) overlay=Image.new('RGBA',(W,H));overlay.paste(layer,(x0,y0));overlay.save(p/'source/post-extension.png') art=Image.alpha_composite(base.convert('RGBA'),overlay).convert('RGB');art.save(p/'source/art-master.png') for name,size in [('art-review.png',(1000,1400)),('art-card-size.png',(500,700)),('art-thumbnail.png',(250,350))]:art.resize(size,Image.Resampling.LANCZOS).save(p/'review'/name) board=Image.new('RGB',(720,1020),'#ece5d5');dd=ImageDraw.Draw(board) for j,(label,im) in enumerate([('v04 — interrupted',base),('v05 — continued',art)]): dd.text((j*360+15,10),label,fill='#263f50') detail=im.crop((265,870,440,2680));detail.thumbnail((340,960));board.paste(detail,(j*360+100,40)) board.save(p/'review/post-before-after.png') join=Image.new('RGB',(840,660),'#ece5d5');dd=ImageDraw.Draw(join) for j,(label,im) in enumerate([('v04',base),('v05',art)]): dd.text((j*420+10,10),label,fill='#263f50') for k,b in enumerate([(305,940,395,1085),(300,2490,390,2670)]): crop=im.crop(b);crop.thumbnail((380,300));crop=crop.resize((180,290 if k==0 else 360));join.paste(crop,(j*420+110,35+k*290)) join.save(p/'review/post-join-detail.png') sha=lambda f:hashlib.sha256(f.read_bytes()).hexdigest() a=np.asarray(base);b=np.asarray(art);diff=np.any(a!=b,axis=2);ys,xs=np.where(diff) assert not diff[:y0].any() and not diff[y1:].any() and not diff[:,:x0].any() and not diff[:,x1:].any() report={'status':'pass','baseSHA256':sha(p/'source/base-v04.png'),'artSHA256':sha(p/'source/art-master.png'),'dimensions':[W,H],'changedPixels':int(diff.sum()),'changedFraction':float(diff.mean()),'differenceBoundsExclusive':[int(xs.min()),int(ys.min()),int(xs.max()+1),int(ys.max()+1)],'authorizedRegion':list(box),'allPixelsOutsidePostRegionIdentical':True,'method':'Deterministic authored overlay, antialiased at 4x; RGB compositing only inside overlay alpha','scope':'Art-only, no card assembly'} (p/'review/source-validation.json').write_text(json.dumps(report,indent=2)+'\n');print(json.dumps(report))