local unicode=require("unicode") local ubyte,uchar,sort,concat=string.byte,unicode.char,table.sort,table.concat local gfx={} local gpu local W,H=0,0 local back,front={},{} local pal,palIdx,palN={},{},0 local ox,oy=0,0 local cx1,cy1,cx2,cy2=1,1,0,0 local dmg={x1=1,y1=1,x2=0,y2=0} local hints={} local clipStack={} gfx.stats={frames=0,set=0,fill=0,copy=0,fg=0,bg=0,cells=0} local function delta(a,b) local dr,dg,db=(a>>16)-(b>>16),((a>>8)&255)-((b>>8)&255),(a&255)-(b&255) return 0.2126*dr*dr+0.7152*dg*dg+0.0722*db*db end for i=0,15 do pal[i]=(i+1)*0x0F0F0F end for r=0,5 do for g=0,7 do for b=0,4 do pal[16+r*40+g*5+b]=(r*51)<<16|((g*255+3)//7)<<8|((b*255+2)//4) end end end palN=256 local function color(rgb) local i=palIdx[rgb] if i then return i end local best,bd=0,math.huge for k=0,15 do local d=delta(pal[k],rgb)if d>16)*5+127)//255 local g=(((rgb>>8)&255)*7+127)//255 local b=((rgb&255)*4+127)//255 local ci=16+r*40+g*5+b if delta(pal[ci],rgb)cx1 then cx1=x end if y>cy1 then cy1=y end if x+w-1=cx1 and y+h-1>=cy1 end function gfx.damage(x,y,w,h) if w<=0 or h<=0 then return end local x2,y2=x+w-1,y+h-1 if x<1 then x=1 end if y<1 then y=1 end if x2>W then x2=W end if y2>H then y2=H end if x>x2 or y>y2 then return end if dmg.x1>dmg.x2 then dmg.x1,dmg.y1,dmg.x2,dmg.y2=x,y,x2,y2 else if xdmg.x2 then dmg.x2=x2 end if y2>dmg.y2 then dmg.y2=y2 end end end function gfx.damageAll()dmg.x1,dmg.y1,dmg.x2,dmg.y2=1,1,W,H end function gfx.isDamaged()return dmg.x1<=dmg.x2 end function gfx.damaged(x,y,w,h) return x<=dmg.x2 and y<=dmg.y2 and x+w-1>=dmg.x1 and y+h-1>=dmg.y1 end function gfx.invalidate() for i=1,W*H do front[i]=-1 end gfx.damageAll() end function gfx.fill(x,y,w,h,bg,fg,ch) x,y=x+ox,y+oy local x2,y2=x+w-1,y+h-1 if xcx2 then x2=cx2 end if y2>cy2 then y2=cy2 end if x>x2 or y>y2 then return end local v=(ch and utf8.codepoint(ch)or 32)|(color(fg or 0xFFFFFF)<<21)|(color(bg)<<29) for yy=y,y2 do local base=(yy-1)*W for i=base+x,base+x2 do back[i]=v end end end function gfx.text(x,y,s,fg,bg) y=y+oy if ycy2 then return end x=x+ox if x>cx2 then return end local fgk=color(fg)<<21 local bgk=bg and(color(bg)<<29) local base=(y-1)*W local ascii=not s:find("[\128-\255]") local n=ascii and#s or unicode.len(s) if x+n-1cx2 then last=cx2-x+1 end if ascii then for i=i0,last do local idx=base+x+i-1 back[idx]=ubyte(s,i)|fgk|(bgk or(back[idx]&(255<<29))) end else local i=0 for _,cp in utf8.codes(s)do i=i+1 if i>last then break end if i>=i0 then local idx=base+x+i-1 back[idx]=cp|fgk|(bgk or(back[idx]&(255<<29))) end end end end function gfx.cell(x,y,ch,fg,bg) x,y=x+ox,y+oy if xcx2 or ycy2 then return end local idx=(y-1)*W+x back[idx]=utf8.codepoint(ch)|(color(fg)<<21)|(bg and(color(bg)<<29)or(back[idx]&(255<<29))) end local function mix(c,rgb,a) local ia=1-a return((c>>16)*ia+(rgb>>16)*a+.5)//1<<16|(((c>>8)&255)*ia+((rgb>>8)&255)*a+.5)//1<<8 |((c&255)*ia+(rgb&255)*a+.5)//1 end function gfx.blend(x,y,w,h,rgb,a,bgOnly) x,y=x+ox,y+oy local x2,y2=x+w-1,y+h-1 if xcx2 then x2=cx2 end if y2>cy2 then y2=cy2 end if x>x2 or y>y2 then return end local key=rgb*1000+(a*999)//1 local map=blendCache[key] if not map then map={};blendCache[key]=map end for yy=y,y2 do local base=(yy-1)*W for i=base+x,base+x2 do local v=back[i] local fi,bi=(v>>21)&255,(v>>29)&255 local nf,nb=map[fi],map[bi] if bgOnly then nf=fi elseif not nf then nf=color(mix(pal[fi],rgb,a));map[fi]=nf end if not nb then nb=color(mix(pal[bi],rgb,a));map[bi]=nb end back[i]=(v&0x1FFFFF)|(nf<<21)|(nb<<29) end end end function gfx.half(x,y,w,rgb,top,a) x,y=x+ox,y+oy if ycy2 then return end local x2=x+w-1 if xcx2 then x2=cx2 end local ch=top and 0x2580 or 0x2584 local base=(y-1)*W local fi=rgb and color(rgb) for i=base+x,base+x2 do local v=back[i] local bi=(v>>29)&255 local f=fi if a then local key=rgb*1000+(a*999)//1 local map=blendCache[key] if not map then map={};blendCache[key]=map end f=map[bi] if not f then f=color(mix(pal[bi],rgb,a));map[bi]=f end end back[i]=ch|(f<<21)|(bi<<29) end end local UPPER,LOWER,FULL=0x2580,0x2584,0x2588 local function halvesOf(v) local cp,f,b=v&0x1FFFFF,(v>>21)&255,(v>>29)&255 if cp==UPPER then return f,b elseif cp==LOWER then return b,f elseif cp==FULL then return f,f end return b,b end local function setHalf(i,isTop,ci) local t,b=halvesOf(back[i]) if isTop then t=ci else b=ci end if t==b then back[i]=32|(t<<21)|(t<<29)else back[i]=UPPER|(t<<21)|(b<<29)end end function gfx.pset(px,py,rgb) local x,y=px+ox,(py+1)//2+oy if xcx2 or ycy2 then return end setHalf((y-1)*W+x,py%2==1,color(rgb)) end function gfx.prect(px,py,pw,ph,rgb) if pw<=0 or ph<=0 then return end local ci=color(rgb) local py2=py+ph-1 local y=py if y%2==0 then for x=px,px+pw-1 do gfx.pset(x,y,rgb)end y=y+1 end local full=(py2-y+1)//2 if full>0 then gfx.fill(px,(y+1)//2,pw,full,rgb)end y=y+full*2 if y<=py2 then for x=px,px+pw-1 do gfx.pset(x,y,rgb)end end end function gfx.prrect(px,py,pw,ph,rgb) gfx.prect(px+1,py,pw-2,ph,rgb) gfx.prect(px,py+1,1,ph-2,rgb) gfx.prect(px+pw-1,py+1,1,ph-2,rgb) end function gfx.pline(x0,y0,x1,y1,rgb) local dx,dy=math.abs(x1-x0),-math.abs(y1-y0) local sx,sy=x0=dy then err=err+dy;x0=x0+sx end if e2<=dx then err=err+dx;y0=y0+sy end end end function gfx.pcircle(cx,cy,r,rgb) for y=-r,r do local w=math.floor(math.sqrt(r*r-y*y)+0.5) gfx.prect(cx-w,cy+y,2*w+1,1,rgb) end end function gfx.bgAt(x,y) x,y=x+ox,y+oy if x<1 or y<1 or x>W or y>H then return 0 end return pal[(back[(y-1)*W+x]>>29)&255] end function gfx.shade(x,y,w,h,a)gfx.blend(x,y,w,h,0,a or 0.45)end local wall,wallW,wallChars function gfx.setWallpaper(str,w,chars)wall,wallW,wallChars=str,w,chars end function gfx.drawWallpaper() if not wall then return gfx.fill(1-ox,1-oy,W,H,0)end local sb=ubyte for y=cy1,cy2 do local base,wb=(y-1)*W,(y-1)*wallW*3 for x=cx1,cx2 do local p=wb+x*3-2 local pat,f,b=sb(wall,p,p+2) if not pat then back[base+x]=32 else back[base+x]=(wallChars and wallChars[pat]or(pat==0 and 32 or 0x2800+pat))|(f<<21)|(b<<29) end end end end function gfx.drawWallpaperScaled(x,y,w,h) if not wall then return end local sb=ubyte local wh=#wall//(wallW*3) for yy=0,h-1 do local sy=yy*wh//h for xx=0,w-1 do local sx=xx*wallW//w local p=(sy*wallW+sx)*3+1 local pat,f,b=sb(wall,p,p+2) local X,Yy=x+xx+ox,y+yy+oy if pat and X>=cx1 and X<=cx2 and Yy>=cy1 and Yy<=cy2 then back[(Yy-1)*W+X]=(wallChars and wallChars[pat]or(pat==0 and 32 or 0x2800+pat))|(f<<21)|(b<<29) end end end end function gfx.image(x,y,img) x,y=x+ox,y+oy local iw,ih,d=img[1],img[2],img[3] for yy=math.max(y,cy1),math.min(y+ih-1,cy2)do local base,ib=(yy-1)*W,(yy-y)*iw-x+1 for xx=math.max(x,cx1),math.min(x+iw-1,cx2)do local k=(ib+xx)*3 local idx=base+xx local bg=d[k] back[idx]=d[k-2]|(color(d[k-1])<<21)|(bg>=0 and(color(bg)<<29)or(back[idx]&(255<<29))) end end end local function halves(cp,fg,bg) if cp==0x2580 then return fg,bg elseif cp==0x2584 then return bg,fg elseif cp==0x2588 then return fg,fg end return bg,bg end function gfx.imageScaled(x,y,w,h,img) local iw,ih,d=img[1],img[2],img[3] local ph,sh=h*2,ih*2 for yy=0,h-1 do for xx=0,w-1 do local X,Yy=x+xx+ox,y+yy+oy if X>=cx1 and X<=cx2 and Yy>=cy1 and Yy<=cy2 then local sx=xx*iw//w local c={} for half=0,1 do local spy=(yy*2+half)*sh//ph local k=((spy//2)*iw+sx+1)*3 local bg=d[k]>=0 and d[k]or 0 local top,bot=halves(d[k-2],d[k-1],bg) c[half+1]=spy%2==0 and top or bot end back[(Yy-1)*W+X]=0x2580|(color(c[1])<<21)|(color(c[2])<<29) end end end end function gfx.blit(grid,gw,gh,x,y) x,y=x+ox,y+oy local sx1,sy1=math.max(x,cx1),math.max(y,cy1) local sx2,sy2=math.min(x+gw-1,cx2),math.min(y+gh-1,cy2) if sx1>sx2 or sy1>sy2 then return end for yy=sy1,sy2 do local base,gbase=(yy-1)*W,(yy-y)*gw-x+1 for xx=sx1,sx2 do back[base+xx]=grid[gbase+xx]end end end function gfx.blitScaled(grid,gw,gh,x,y,dw,dh) local ph,sh=dh*2,gh*2 local rowSrc={} for py=0,ph-1 do rowSrc[py]=py*sh//ph end for yy=0,dh-1 do local Yy=y+yy+oy if Yy>=cy1 and Yy<=cy2 then local spT,spB=rowSrc[yy*2],rowSrc[yy*2+1] local cT,cB=(spT//2)*gw,(spB//2)*gw local loT,loB=spT%2==1,spB%2==1 local base=(Yy-1)*W for xx=0,dw-1 do local X=x+xx+ox if X>=cx1 and X<=cx2 then local sx=xx*gw//dw+1 local vt,vb=grid[cT+sx],grid[cB+sx] local t,b do local cp,f,bg=vt&0x1FFFFF,(vt>>21)&255,(vt>>29)&255 local up,lo=bg,bg if cp==0x2580 then up=f elseif cp==0x2584 then lo=f elseif cp==0x2588 then up,lo=f,f end t=loT and lo or up end do local cp,f,bg=vb&0x1FFFFF,(vb>>21)&255,(vb>>29)&255 local up,lo=bg,bg if cp==0x2580 then up=f elseif cp==0x2584 then lo=f elseif cp==0x2588 then up,lo=f,f end b=loB and lo or up end back[base+X]=(t==b and 32 or 0x2580)|(t<<21)|(b<<29) end end end end end function gfx.scrollHint(x,y,w,h,dy) local last=hints[#hints] if last and not last.move and last[1]==x and last[2]==y and last[3]==w and last[4]==h then last[5]=last[5]+dy else hints[#hints+1]={x,y,w,h,dy} end end function gfx.moveHint(x,y,w,h,dx,dy) if dx==0 and dy==0 then return end if x<1 or y<1 or x+w-1>W or y+h-1>H then return end if x+dx<1 or y+dy<1 or x+dx+w-1>W or y+dy+h-1>H then return end hints[#hints+1]={x,y,w,h,dx,dy,move=true} end local function applyMove(h) local x,y,w,hh,dx,dy=h[1],h[2],h[3],h[4],h[5],h[6] gpu.copy(x,y,w,hh,dx,dy) gfx.stats.copy=gfx.stats.copy+1 local tmp,n={},0 for r=y,y+hh-1 do local b=(r-1)*W for c=x,x+w-1 do n=n+1;tmp[n]=front[b+c]end end n=0 for r=y+dy,y+dy+hh-1 do local b=(r-1)*W for c=x+dx,x+dx+w-1 do n=n+1;front[b+c]=tmp[n]end end end local rY,rX,rS,rK,rH,rSp,order={},{},{},{},{},{},{} local cps={} local function applyHint(h) local x,y,w,hh,dy=h[1],h[2],h[3],h[4],h[5] if dy==0 or math.abs(dy)>=hh then return end if x<1 or y<1 or x+w-1>W or y+hh-1>H then return end local srcY=dy>0 and y+dy or y local n=hh-math.abs(dy) gpu.copy(x,srcY,w,n,0,-dy) gfx.stats.copy=gfx.stats.copy+1 if dy>0 then for r=0,n-1 do local d,s=(y+r-1)*W,(y+r+dy-1)*W for c=x,x+w-1 do front[d+c]=front[s+c]end end else for r=n-1,0,-1 do local d,s=(y+r-dy-1)*W,(y+r-1)*W for c=x,x+w-1 do front[d+c]=front[s+c]end end end end function gfx.flush() if not gpu then return 0 end local st=gfx.stats for i=1,#hints do local h=hints[i];if h.move then applyMove(h)else applyHint(h)end;hints[i]=nil end local n=0 local fx1,fx2=dmg.x1,dmg.x2 local y1,y2=dmg.y1,dmg.y2 for y=y1,y2 do do local base=(y-1)*W local x=fx1 while x<=fx2 do local i=base+x local v=back[i] if v~=front[i]then local key=v>>21 local start,lastDiff=x,x local space=(v&0x1FFFFF)==32 x=x+1 while x<=fx2 do local v2=back[base+x] if(v2>>21)~=key then break end if v2~=front[base+x]then lastDiff=x elseif x-lastDiff>4 then break end x=x+1 end local nc=0 for j=base+start,base+lastDiff do local c=back[j]&0x1FFFFF if c~=32 then space=false end nc=nc+1 cps[nc]=c front[j]=back[j] end n=n+1 rY[n],rX[n],rK[n],rH[n],rSp[n]=y,start,key,1,space if space then rS[n]=nc else rS[n]=nc<200 and uchar(table.unpack(cps,1,nc))or nil if not rS[n]then local parts={} for k=1,nc,200 do parts[#parts+1]=uchar(table.unpack(cps,k,math.min(nc,k+199)))end rS[n]=concat(parts) end end st.cells=st.cells+nc x=lastDiff+1 else x=x+1 end end end end local stack,alive={},{} for k=1,n do alive[k]=true if rSp[k]then local id=rX[k]*4096+rS[k]*8+0 local sk=id..":"..(rK[k]>>8) local prev=stack[sk] if prev and rY[prev]+rH[prev]==rY[k]then rH[prev]=rH[prev]+1 alive[k]=false else stack[sk]=k end end end local m=0 for k=1,n do if alive[k]then m=m+1 order[m]=k local bgi,fgi=rK[k]>>8,rK[k]&255 rK[k]=bgi*512+(rSp[k]and 0 or fgi+1) end end for k=m+1,#order do order[k]=nil end sort(order,function(a,b)return rK[a]0 and fgp-1~=curFg then gpu.setForeground(fgp<=16 and fgp-1 or pal[fgp-1],fgp<=16);curFg=fgp-1;st.fg=st.fg+1 end if rSp[k]then if rH[k]>1 then gpu.fill(rX[k],rY[k],rS[k],rH[k]," ");st.fill=st.fill+1 else gpu.set(rX[k],rY[k],string.rep(" ",rS[k]));st.set=st.set+1 end else gpu.set(rX[k],rY[k],rS[k]);st.set=st.set+1 end end for k=1,n do rS[k]=nil end st.frames=st.frames+1 dmg.x1,dmg.y1,dmg.x2,dmg.y2=1,1,0,0 return m end function gfx.peek(x,y) local v=back[(y-1)*W+x] return v&0x1FFFFF,pal[(v>>21)&255],pal[(v>>29)&255] end function gfx.dump() local rows={} for y=1,H do local t={} for x=1,W do t[x]=uchar(back[(y-1)*W+x]&0x1FFFFF)end rows[y]=concat(t) end return rows end return gfx