/* tvgen - draw pm5544-like television test cards. * Copyright (C) 2007, 2009, 2012, Servaas Vandenberghe. * * The tvgen code below is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with tvgen: see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * tvgen-1.2/tvgen.asy http://picaros.org/ftp/soft/tvgen-1.2.tgz * This asy script generates pm5544-like television test cards. The image * parameters were derived from a 1990 recording. The basic parameters * conform to itu-r bt.470, bt.601, and bt.709. There is no unique image * since local variants exist and parameters have varied over time. */ //papertype="a4"; import plain; int verbose=settings.verbose/*+2*/; /* uncomment for debug info */ /* tv dot coordinates --> PS points */ pair tvps(real col, real row, real xd, real yd, int Nv) { real psx, psy; psx=col*xd; psy=(Nv-row)*yd; return (psx,psy); } path tvrect(int lc, int tr, int rc, int br, real xd, real yd, int Nv) { real lx, ty, rx, by; pair[] z; lx=lc*xd; ty=(Nv-tr)*yd; rx=rc*xd; by=(Nv-br)*yd; /* bl br tr tl */ z[0]=(lx, by); z[1]=(rx, by); z[2]=(rx, ty); z[3]=(lx, ty); return z[0]--z[1]--z[2]--z[3]--cycle; } /********************* horizontal castellations ********************/ /* Draw a horizontal red line in the top left and the bottom right * castellation. These testlines disappear if the monitor is not set * in a dot-exact mode. An example is image crop due to overscan. * * For 625 line systems any analog-compatible processing removes * these red testlines since the first halfline of the odd field and * the last halfline of the even field are ignored. A full 576 * visible line frame often results via a final copy paste operation. */ void castelhor(int colortv, int[] rccoll, int[] rccolr, int cmaxi, int Nh, int topdist, int botdist, pen pdef, real xd, real yd, int Nv) { pen pblack, pwhite, pred; int i; pblack = pdef+gray(0.0); pwhite = pdef+gray(1.0); pred = pdef+rgb(0.75, 0, 0); /** top and bottom: white corners. **/ for (i=-1; i<=cmaxi; ++i) { pen pcast; int inext, lc, rc, tr, br; path zzc; inext = i+1; if (inext%2 == 0) { pcast = pwhite; } else { pcast = pblack; } if (i >= 0) { lc = rccolr[i]; } else { lc = 0; } if (inext <= cmaxi) { rc = rccoll[inext]; } else { rc = Nh; } if (i == 0 && colortv > 0 && topdist > 1) { path zzr; zzr = tvrect(lc,0, rc,1, xd,yd,Nv); fill(zzr, p=pred); tr = 1; } else { tr = 0; } zzc = tvrect(lc,tr, rc,topdist, xd,yd,Nv); fill(zzc, p=pcast); if (inext == cmaxi && colortv > 0 && botdist+1 < Nv) { path zzr; zzr = tvrect(lc,Nv-1, rc,Nv, xd,yd,Nv); fill(zzr, p=pred); br = Nv-1; } else { br = Nv; } zzc = tvrect(lc,botdist, rc,br, xd,yd,Nv); fill(zzc, p=pcast); } return; } /********************* vertical castellations ********************/ /* The bottom right red rectangle tests for a non causal color FIR * filter in the receiver. The last 2..4 dots then typically appear * colorless, green, or cyan. * * This stems from the fact that the chroma subcarrier is of lower * bandwidth than luma and thus continues after the last active sample. * These trailing (y,u,v) samples result from an abrupt signal to zero * transition and depend on the transmit and receive filters. Samples * from VHS, system B/G/D/K, system I, or a DVD player output are * different. Nevertheless, a sharpening filter uses this data and so * adds false color to the last dots. */ void castelver(int colortv, int leftdist, int rightdist, int Nh, int[] rcrowb, int[] rcrowt, int rmaxi, pen pdef, real xd, real yd, int Nv) { pen pblack, pwhite; int i; pblack = pdef+gray(0.0); pwhite = pdef+gray(1.0); for (i=0; i0) { pcastr = pdef+rgb(0.75,0.0,0); } else { pcastr = pcastl; } tr=rcrowb[i]; br=rcrowt[i+1]; zzc=tvrect( 0,tr, leftdist,br, xd,yd,Nv); fill(zzc, p=pcastl); zzc=tvrect(rightdist,tr, Nh,br, xd,yd,Nv); fill(zzc, p=pcastr); } return; } /********************* image aspect ratio markers ********************/ void rimarkers(real rimage, int Nh, int Nhc, int os, int Nvc, int Nsy, pen pdef, real xd, real yd, int Nv) { int[] ridefN={ 4, 16 }; int[] ridefD={ 3, 9 }; int i; for (i=0; i<2; ++i) { real rid=ridefN[i]/ridefD[i]; if (rimage>rid) { int off, offa, offb; /* Nhdef=Nh*rid/rimage */ off=round(Nh*rid/rimage/2); offa=off+os; offb=off-os; // write(offa,offb); if (2*offa 0) { /* black, vertical gridlines redrawn below */ pair[] z; int col; z[0]=rcright[divsy]; col = rccolc[divsx+1]; z[1]=tvps(col,rcrowc[divsy], xd,yd,Nv); z[2]=tvps(col,rcrowc[divsy-1], xd,yd,Nv); col = rccolc[divsx]; z[3]=tvps(col,rcrowc[divsy-1], xd,yd,Nv); z[4]=tvps(col,rcrowc[divsy], xd,yd,Nv); z[5]=rcleft[divsy]; z[6]=rcleft[divsy+1]; z[7]=tvps(col,rcrowc[divsy+1], xd,yd,Nv); z[8]=tvps(col,rcrowc[divsy+2], xd,yd,Nv); col = rccolc[divsx+1]; z[9]=tvps(col,rcrowc[divsy+2], xd,yd,Nv); z[10]=tvps(col,rcrowc[divsy+1], xd,yd,Nv); z[11]=rcright[divsy+1]; fill(z[1]--z[2]--z[3]--z[4] //--z[5]--z[6] --arc(ccenter, z[5], z[6]) --z[7]--z[8]--z[9]--z[10] //--z[11]--z[0] --arc(ccenter,z[11], z[0]) --cycle, p=pblack); } else { /* 3 rows of black squares inside the gratings */ int i, imax = divsy+1; for (i=divsy-1; i<=imax; ++i) { /* all 3 rows */ int lmaxoff, lmincol, lmaxcol; int inext = i+1; int tr, br, j; /* XXX rcoff is relative to ccenter */ lmaxoff = min(floor(rcoff[i]), floor(rcoff[inext])); lmincol = Nhc-lmaxoff; lmaxcol = Nhc+lmaxoff; /* square top and bottom */ tr = rcrowb[i]; br = rcrowt[inext]; for (j=0; j 1) { write("centerline long : rows tr br ", rows, tr, br); } zz=tvrect(Nhc-os, tr, Nhc+os, br, xd,yd,Nv); fill(zz, p=pwhite); zz=tvrect(Nhc-maxoff,Nvc-1, Nhc+maxoff,Nvc+1, xd,yd,Nv); fill(zz, p=pwhite); /* vertical short lines */ rows=min(Nvc-rcrowc[divsy], rcrowc[divsy+1]-Nvc); tr=Nvc-rows; br=Nvc+rows; if (verbose > 1) { write("centerline short: rows tr br ", rows, tr, br); } if (colortv > 0) { int i; for (i=0; i<=cmaxi; ++i) { int coll, colr; coll=rccoll[i]; colr=rccolr[i]; if (mincol<=coll && colr<=maxcol) { path zzv; zzv=tvrect(coll, tr, colr, br, xd,yd,Nv); fill(zzv, p=pwhite); } } } return; } /************************ topbw **************************************/ void topbw(int[] coff, int Nhc, int os, int urow, int trow, int brow, pair ccenter, pair rclt, pair rclb, pair rcrt, pair rcrb, pen pdef, real xd, real yd, int Nv) { pen pblack=pdef+gray(0.0), pwhite=pdef+gray(1.0); pair[] ze; path zext, zref, zint; int off, col, cr; off=ceil((coff[2]+coff[3])/2); ze[0]=tvps(Nhc+off,trow, xd,yd,Nv); ze[1]=rcrt; ze[2]=rclt; ze[3]=tvps(Nhc-off,trow, xd,yd,Nv); ze[4]=tvps(Nhc-off,brow, xd,yd,Nv); col=Nhc-coff[2]-os; ze[5]=tvps(col,brow, xd,yd,Nv); ze[6]=tvps(col,trow, xd,yd,Nv); cr=col+3*os; /* reflection test black pulse */ zref=tvrect(col,trow, cr,brow, xd,yd,Nv); ze[7]=tvps(cr,trow, xd,yd,Nv); ze[8]=tvps(cr,brow, xd,yd,Nv); ze[9]=tvps(Nhc+off,brow, xd,yd,Nv); //dot(ze); zext=ze[0] // --ze[1]--ze[2] --arc(ccenter, ze[1], ze[2]) --ze[3]--ze[4]--ze[5]--ze[6]--ze[7]--ze[8]--ze[9]--cycle; off=ceil((coff[1]+coff[2])/2); zint=tvrect(Nhc-off,urow, Nhc+off,trow, xd,yd,Nv); /* paths are completely resolved; no free endpoint conditions */ fill(zext^^reverse(zint), p=pwhite); fill(zint, p=pblack); fill(zref, p=pblack); fill(arc(ccenter,rclt,rclb)--ze[4]--ze[3]--cycle, p=pblack); fill(arc(ccenter,rcrb,rcrt)--ze[0]--ze[9]--cycle, p=pblack); return; } /************************ testtone **************************************/ /* x on circle -> return y>=0 * in: * x x-coordinate relative to origin * crad circle radius in y units, true size=crad*yd */ real testcircx(real x, real crad, real xd, real yd) { real relx, ph, y; relx=x*xd/yd/crad; if (relx>1) { ph=0; } else { ph=acos(relx); } y=crad*sin(ph); // or (x*xd)^2+(y*yd)^2=(crad*yd)^2 return y; } /* y on circle -> return x>=0 */ real testcircy(real y, real crad, real xd, real yd) { real rely, ph, x; rely=y/crad; if (rely>1) { ph=pi/2; } else { ph=asin(rely); } x=crad*cos(ph)*yd/xd; // or (x*xd)^2+(y*yd)^2=(crad*yd)^2 return x; } /* brow>trow && xb>xt */ void testtone(real Tt, int trow, int brow, real ccol, real crow, real crad, pen pdef, real xd, real yd, int Nv) { int blocks, i; real yt, xt, yb, xb, Ttt=Tt/2; pair ccenter; yt=crow-trow; xt=testcircy(yt, crad, xd, yd); yb=crow-brow; xb=testcircy(yb, crad, xd, yd); //write('#xt yt\t',xt,yt); write('#xb yb\t',xb,yb); ccenter=tvps(ccol,crow, xd,yd,Nv); blocks=floor(2*xb/Tt); for (i=-blocks-1; i<=blocks; ++i) { real tl, tr; path zz; tl=max(-xb,min(i*Ttt,xb)); /* limit [-xb..xb] */ tr=max(-xb,min((i+1)*Ttt,xb)); if (tl<-xt && tr<=-xt || tr>xt && tl>=xt) { /* top full circle */ pair[] z; real yl, yr; yl=testcircx(tl, crad, xd, yd); yr=testcircx(tr, crad, xd, yd); z[0]=tvps(ccol+tl,brow, xd,yd,Nv); z[1]=tvps(ccol+tr,brow, xd,yd,Nv); z[2]=tvps(ccol+tr,crow-yr, xd,yd,Nv); z[3]=tvps(ccol+tl,crow-yl, xd,yd,Nv); zz=z[0]--z[1]--arc(ccenter,z[2],z[3])--cycle; } else if(tl<-xt) { /* tl in circel, tr not, partial */ pair[] z; real yl; yl=testcircx(tl, crad, xd, yd); z[0]=tvps(ccol+tl,brow, xd,yd,Nv); z[1]=tvps(ccol+tr,brow, xd,yd,Nv); z[2]=tvps(ccol+tr,trow, xd,yd,Nv); z[3]=tvps(ccol-xt,trow, xd,yd,Nv); z[4]=tvps(ccol+tl,crow-yl, xd,yd,Nv); zz=z[0]--z[1]--z[2]--arc(ccenter,z[3],z[4])--cycle; } else if(tr>xt) { /* tr in circle, tl not, partial */ pair[] z; real yr; yr=testcircx(tr, crad, xd, yd); z[0]=tvps(ccol+tl,brow, xd,yd,Nv); z[1]=tvps(ccol+tr,brow, xd,yd,Nv); z[2]=tvps(ccol+tr,crow-yr, xd,yd,Nv); z[3]=tvps(ccol+xt,trow, xd,yd,Nv); z[4]=tvps(ccol+tl,trow, xd,yd,Nv); zz=z[0]--z[1]--arc(ccenter,z[2],z[3])--z[4]--cycle; } else { /* full block */ pair[] z; z[0]=tvps(ccol+tr,trow, xd,yd,Nv); z[1]=tvps(ccol+tl,trow, xd,yd,Nv); z[2]=tvps(ccol+tl,brow, xd,yd,Nv); z[3]=tvps(ccol+tr,brow, xd,yd,Nv); zz=z[0]--z[1]--z[2]--z[3]--cycle; } if (tl1) { thetaret=0; } else { real dpi=2*pi; cycles-=coverflow*sgn(cycles); thetaret=theta+cycles*dpi; /* cycles=(-1 .. 1) */ if (thetaret>pi) { thetaret-=dpi; } else if (thetaret<-pi) { thetaret-=dpi; } } //write("addphase: ", step, theta, thetaret); return thetaret; } void testfreqs(real[] ftones, int[] coff, int Nhc, int trow,int crow,int brow, pair ccenter, pair rclt, pair rclb, pair rcrt, pair rcrb, pen pdef, real xd, real yd, int Nv) { int[] divc; real[] divfl, divfr; int i, divs, coffmax, off, divnext; real fl, fr, thr, thl; /* Segment info for PAL continental test card * segment i extends from (divc[i] .. divc[i+1]) with frequency divf[i] */ divs=2; // the number of segments to the right, total=2*divs+1 divc[0]=0; for (i=0; i<=divs; ++i) { int ii=i*2, il=divs-i, ir=divs+i; divc[i+1]=ceil((coff[ii]+coff[ii+1])/2); /* xdot distance to center */ divfl[i]=ftones[divs-i]; divfr[i]=ftones[divs+i]; } coffmax=divc[divs+1]; int trowlim=coff[0]; int tr; tr=crow; divnext=0; fl=0; fr=0; thl=0; /* ={ 0, -pi/2 } : initial angle at center vertical line Nhc */ thr=thl; /* draw a vertical line at off..off+1, use theta for off+1/2 */ for (off=0; off0) { if (verbose > 1) write("right ears"); cy=cyr; cu=cur; cv=cvr; } else { if (verbose > 1) write("left ears"); cy=cyl; cu=cul; cv=cvl; } lcol=Nhc+dright*coffa[5]; ccol=Nhc+dright*coff[6]; cicol=Nhc+dright*coffa[6]; rcol=Nhc+dright*coffb[7]; int urow, trow, crow, brow, arow; urow=rcrowb[divsy-5]; trow=rcrowt[divsy-3]; crow=Nvc; brow=rcrowb[divsy+4]; arow=rcrowt[divsy+6]; z[0]=tvps(ccol,urow, xd,yd,Nv); z[1]=tvps(ccol,trow, xd,yd,Nv); z[2]=tvps(cicol,trow, xd,yd,Nv); z[3]=tvps(cicol,crow, xd,yd,Nv); z[4]=tvps(rcol,crow, xd,yd,Nv); z[5]=tvps(rcol,urow, xd,yd,Nv); zz[0]=z[0]--z[1]--z[2]--z[3]--z[4]--z[5]--cycle; zz[1]=tvrect(lcol,urow, ccol,trow, xd,yd,Nv); zz[2]=tvrect(lcol,brow, ccol,arow, xd,yd,Nv); z[0]=tvps(ccol,arow, xd,yd,Nv); z[1]=tvps(ccol,brow, xd,yd,Nv); z[2]=tvps(cicol,brow, xd,yd,Nv); z[3]=tvps(cicol,crow, xd,yd,Nv); z[4]=tvps(rcol,crow, xd,yd,Nv); z[5]=tvps(rcol,arow, xd,yd,Nv); zz[3]=z[0]--z[1]--z[2]--z[3]--z[4]--z[5]--cycle; for (i=0; i<4; ++i) { real y, u, v, A, ph, By, Ry, Gy, R, G, B; y=cy[i]; u=cu[i]; v=cv[i]; A=hypot(u,v); ph= (u!=0 || v!=0) ? atan2(v,u) : 0.0; if (v>=0) { if (ph<0) ph=ph+pi; } else { if (ph>0) ph=ph-pi; } if (A>0) { u=u/A*cI; v=v/A*cI; } By=u/wu; Ry=v/wv; Gy=(-wr*Ry-wb*By)/wg; //write(y,Gy,A,ph*180/pi); R=Ry+y; G=Gy+y; B=By+y; if (verbose > 1) write(y*1000, round(R*1000), round(G*1000), round(B*1000)); fill(zz[i], p=pdef+rgb(R,G,B)); } return; } /****************************** NTSC bars ***********************************/ /* amplitude equals color burst smpte (pm: -V +U) * y campl sat R G B * left 0.5 0.21 70% -I? * right 0.5 0.17 60% +Q? */ void ntscbars(int[] rccoll, int[] rccolr, int divsx, int[] rcrowt, int[] rcrowb, int divsy, int dright, pen pdef, real xd, real yd, int Nv) { /* The amplitude of (i,q) as seen on a vectorscope, * max 0.292 Vn for 100% saturation in I==0 ears. * burst: 0.143 Vcvbs, 20 IRE or 0.200 V normalized. * pedestal: (yp,up,vp)=(p,0,0)+(1-p)*(y,u,v), p=0.075. * choice: equal amplitude for colorburst and subcarrier. */ real campl=0.200/0.925; /* wg=0.587, y=wr*R+wg*G+wb*B */ real wr=0.299, wb=0.114, wg=1-wr-wb; /* iT : iq -> RyBy : rotation+scaling */ real iT11=0.95, iT12=0.62, iT21=-1.11, iT22=1.71; /* bars -2 -1 0 1 2 */ real[] cyl={ 0.50, 0.50, 1, 0.50, 0.50 }; real[] cil={ 0, 0, 0, -1, 1 }; real[] cql={ -1, 1, 0, 0, 0 }; int[] indl={ -7, -8, 0, 8, 7 }; real cy, ci, cq; int rmaxi, dri, ind, ibase, lcol, rcol, i; rmaxi=2*divsy+1; if (dright<-2 || dright>2) { dri=2; } else { dri=2+dright; } cy=cyl[dri]; ci=cil[dri]; cq=cql[dri]; ind=indl[dri]; ibase=divsx+ind; lcol=rccolr[ibase]; rcol=rccoll[ibase+1]; real A, By, Ry, Gy, R, G, B; A=hypot(ci,cq); if (A>0) { ci=ci/A*campl; cq=cq/A*campl; } Ry=iT11*ci+iT12*cq; By=iT21*ci+iT22*cq; Gy=(-wr*Ry-wb*By)/wg; //write(cy,Ry,Gy,By); R=Ry+cy; G=Gy+cy; B=By+cy; if (verbose > 1) write(ind, cy*1000, round(ci*1000), round(cq*1000), round(R*1000), round(G*1000), round(B*1000)); for (i=0; i0) { trow=rcrowb[i]; } else { trow=floor((rcrowb[i]+rcrowt[inext])/2); } if (inext768 rerastering * 16 720->768 rerastering */ access settings; usersetting(); if (bsys<0 || bsys>12 || colortv<0 || colortv>3 || os<=0 || os>16) { write("Error: bad user input: bsys, colortv, os=\t", bsys, colortv, os); abort("Bad option -u bsys=N ?"); } int[] bNdot= { 12, 16, 12, 16, 1, 1, 1, 64, 10, 8, 10, 1, 1 }; int[] bDdot= { 11, 15, 11, 11, 1, 1, 1, 45, 11, 9, 11, 1, 1 }; int[] bNh= { 704, 720, 720, 720, 768, 768, 786, 720, 704, 720, 720, 1920, 1600 }; int[] bNv= { 576, 576, 576, 576, 576, 576, 576, 576, 480, 480, 480, 1080, 1200 }; real[] bfs= { 13.5,13.5,13.5,13.5, 14.75,14.4,14.75,13.5, 13.5,13.5,13.5, 36, 30 }; int[] bNsy= { 42, 42, 42, 42, 42, 42, 42, 42, 34, 34, 34, 78, 90 }; int[] bNsh= { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; /* active lines for a 625 line frame * The number of active video lines decreased around 1997. * old: 3 run in + 575 visible + 3 run out = 581 lines * new: 6 teletext and WSS + 575 visible * Hence the image center shifted down by 3 lines. Thus * old TV + new testcard = bottom is cut off, * new TV + old testcard = top is cut off. * * To generate the old testcard either use Nv=582 Nsh=0 or Nv=576 Nsh=3. * * aspect ratio * rimage=xsize/ysize rimage=rdot*Nh/Nv * Nh=704 dots * Nv=576 lines * rd=ri*Nv/Nh=4/3*9/11=12/11 * * Nv: 480=2^5*3*5 576=2^6*3^2 * Nh: 704=2^6*11 720=2^4*3^2*5 * * horizontal line distance for pre 1997 test pattern * top 8 lines, 13 squares of Ny=43 lines, bottom 9 lines * top 12 lines, 13 squares of Ny=42 lines, bottom 18 lines * pairs are from odd even field * Interlace test: Ny must be odd for a cross-hatch without centerline. * * squares: ly=Nsy, lx=rd*Nsx, lx=ly ==> Nsx=Nsy/rd={ 39.4, 38.5 } * x line width 230 ns -> 3 dots * bottom 2.9us red -> 39.15 dots * * resolution DPI from image aspect ratio * Rv=Nv/ly, ly=4in * ri=Ni/Di, Ni={ 4, 15, 16} Di={ 3, 11, 9} * lx=ri*ly * * Rh=Nh/lx=Di*(Nh/(Ni*ly)) * integer Rh: * Ni=4 ri=4/Di => Nh=k*16 * Ni=15 ri=15/Di => Nh=k*60 * Ni=16 ri=16/Di => Nh=k*64 * * resolution DPI from dot aspect ratio, general algorithm, * * rd=Nd/Dd=ldx/ldy * * assume 1 dot = Nd x Dd square subdots at a resolution of k, in dpi, then * * ldx=Nd/k, ldy=Dd/k ==> Rh=k/Nd, Rv=k/Dd * * choosing k=m*Nd*Dd for integer Rh and Rv gives * * ldx=1/(m*Dd), ldy=1/(m*Nd), Rh=m*Dd, Rv=m*Nd * * and * * lx=Nh*ldx=Nh/(m*Dd), ly=Nv*ldy=Nv/(m*Nd) * * so choose m for the intended height Ly, in inch, as * * m=round(Nv/(Ly*Nd)) * * which limits Ly<=Nv/Nd since Rv>=Nd. */ //cm=72/2.540005; real Ly, ly, lx, ysize, xsize, rimage, xd, yd, pwidth; int Nd, Dd, m, Nh, Nv, Nshift, Na, Nsy; real fs, Ttone; Nd=bNdot[bsys]; Dd=bDdot[bsys]*os; Nh=bNh[bsys]*os; Nv=bNv[bsys]; Ly=4; // 4 inch vertical size m=floor(0.5+Nv/(Ly*Nd)); if (m < 1) m=1; ly=Nv/(m*Nd); lx=Nh/(m*Dd); ysize=ly*1inch; xsize=lx*1inch; rimage=xsize/ysize; if (verbose > 1) { write("#Nd Dd m ri:\t", Nd, Dd, m, rimage); } //size(xsize, ysize, Aspect); // should not have any effect Nsy=bNsy[bsys]; // grating size in lines 42,43 or 34,35 Nshift=bNsh[bsys]; // shift image up: pre 1997 =3, 2007 =0 fs=1e6*bfs[bsys]*os; Na=0; // add 1,0,-1 to height of hor center squares for even Na+Nsy Ttone=fs/250e3; // period of ft=250 kHz, fs/ft=54 real[] ftones={0.8e6/fs, 1.8e6/fs, 2.8e6/fs, 3.8e6/fs, 4.8e6/fs}; xd=xsize/Nh; yd=ysize/Nv; pwidth=min(abs(xd),abs(yd)); pen pdefault = squarecap+linewidth(pwidth); pen pblack = pdefault+gray(0.0); pen pwhite = pdefault+gray(1.0); /**** calculate grating repeats and size in tv dots ****/ /* horizontal lines */ int divsy, rdisty, Nvc, Nt, Nb, rmaxi; Nvc=floor(Nv/2)-Nshift; /* top half picture (Nv-2)/2-(Nsy+Na)/2 dots for divisions of Nsy dots */ divsy=floor(((Nv-2-Na)/Nsy-1)/2); rdisty=Na+Nsy*(1+2*divsy); /* first guess free lines top and bottom */ Nt=Nvc-ceil(rdisty/2); Nb=Nv-Nt-rdisty; if (verbose > 1) { write('#divsy t b: \t', divsy, Nt, Nb); } rmaxi=2*divsy+1; /* Nsyc: center square height * line pairing test: verify distance of center to top and bot * distance is odd ==> top=even/odd, cent=odd/even, bot=even/odd * * Nsyc odd: not possible * * Nsyc even: * Nsyc/2 odd --> OK * Nsyc/2 even --> stagger the raster one line upwards * * rcrowt top dist of hor line * rcrowc true center for color info, distance to top of image. * rcrowb bot dist of hor line * * offd = offu-Nsyc * Nt = Nvc-(offu+divsy*Nsy); * Nb = Nv-( Nvc-(offd-divsy*Nsy) ); * ==> Nt+Nb = Nv-Nsyc-2*divsy*Nsy */ int Nsyc, offu, offd, Nyst=0, i; int[] rcrowt, rcrowc, rcrowb; Nsyc=Nsy+Na; offu=floor(Nsyc/2); offd=offu-Nsyc; if (Nsyc%2 != 0) { Nyst=1; } else if (Nsyc%4 == 0) { Nyst=1; /* stagger */ } for (i=0; i<=divsy; ++i) { int iu, id, ou, od, ru, rd; iu=divsy-i; id=divsy+i+1; ou=offu+Nsy*i; od=offd-Nsy*i; if (verbose > 1) { write(ou,od); } rcrowc[iu]=Nvc-ou; rcrowc[id]=Nvc-od; ru=Nvc-(ou+Nyst); rd=Nvc-(od+Nyst); rcrowt[iu]=ru-1; rcrowb[iu]=ru+1; rcrowt[id]=rd-1; rcrowb[id]=rd+1; } Nt=floor((rcrowt[0]+rcrowb[0])/2); Nb=Nv-Nt-Nsyc-2*Nsy*divsy; if (verbose > 1) { write('#st t b: \t', Nyst, Nt, Nb); } /* vertical lines * (Nh-2*os)/2-Nsx/2 dots available for divisions of Nsx dots. * At least 5 dots margin left and right ==> use -10*os */ real lsq, Nsx, rdistx; int divsx, Nhc, Nl, Nr, cmaxi; lsq=Nsy*yd; Nsx=lsq/xd; /* floating point */ divsx=floor(((Nh-10*os)/Nsx-1)/2); Nhc=round(Nh/2); rdistx=(1+2*divsx)*Nsx; Nl=Nhc-round(rdistx/2); if (verbose > 1) { write('#divsx Nsx l:\t', divsx, Nsx, Nl); } cmaxi=2*divsx+1; int[] coff, coffl, coffr; int[] rccoll, rccolc, rccolr; for (i=0; i<=divsx; ++i) { int off, offl, offr, il, ir; real cdist; cdist=Nsx*(1+2*i); /* horizontal distance 2 symmetrical vert lines */ off=round(cdist/2); // write(cdist, off); offl=off-os; offr=off+os; coff[i]=off; coffl[i]=offl; coffr[i]=offr; if (verbose > 1) { write(cdist, off); } il=divsx-i; ir=divsx+i+1; rccoll[il]=Nhc-offr; rccolc[il]=Nhc-off; rccolr[il]=Nhc-offl; rccoll[ir]=Nhc+offl; rccolc[ir]=Nhc+off; rccolr[ir]=Nhc+offr; } Nl=rccolc[0]; Nr=Nh-rccolc[cmaxi]; if (verbose > 1) { write('#divsx Nsx l r:\t', divsx, Nsx, Nl, Nr); } /**** draw gray background ****/ { path zz; //zz=tvrect(0,0, Nh,Nv, xd,yd,Nv); /* keep white canvas for castellations */ zz=tvrect(rccoll[0],rcrowt[0], rccolr[cmaxi],rcrowb[rmaxi], xd,yd,Nv); fill(zz, p=pdefault+gray(0.5)); //dot(zz); } /**** draw center circle ****/ real cx, cy, crad; pair ccenter; path ccirc; cx=Nh/2; cy=Nv/2-Nshift; crad=6*Nsy; if (Nv%2 != 0) { crad+=0.5; } ccenter=tvps(cx,cy, xd,yd,Nv); ccirc=circle(ccenter, crad*yd); if (colortv<=0) { draw(ccirc, p=pwhite+linewidth(2*yd)); } /**** draw 2*divsy+2 horizontal gridlines ****/ real[] rcang, rcoff; pair[] rcright, rcleft; int i; for (i=0; i<=rmaxi; ++i) { real y, ph, x; path zzh; pair zd; zzh=tvrect(0,rcrowt[i], Nh,rcrowb[i], xd,yd,Nv); fill(zzh, p=pwhite); y=cy-rcrowc[i]; if (abs(y)4/3) { rimarkers(rimage, Nh, Nhc, os, Nvc, Nsy, pwhite, xd, yd, Nv); } /****** line pairing center ******/ centerline(colortv, rccoll, rccolc, rccolr, divsx, Nhc, os, rcrowt, rcrowc, rcrowb, divsy, Nvc, ccenter, rcoff, rcright, rcleft, pdefault, xd, yd, Nv); if (colortv>0) { /* topbw structure */ topbw(coff, Nhc, os, rcrowc[divsy-5], rcrowc[divsy-4], rcrowc[divsy-3], ccenter, rcleft[divsy-4], rcleft[divsy-3], rcright[divsy-4], rcright[divsy-3], pdefault, xd, yd, Nv); /* 250 kHz */ testtone(Ttone, rcrowc[divsy-3], rcrowc[divsy-2], cx, cy, crad, pdefault, xd, yd, Nv); /* color bars */ colorbars(coff, Nhc, rcrowc[divsy-2], rcrowc[divsy-1], rcrowc[divsy], ccenter, rcleft[divsy-2], rcleft[divsy], rcright[divsy-2], rcright[divsy], pdefault, xd, yd, Nv); /* test frequencies */ testfreqs(ftones, coff, Nhc, rcrowc[divsy+1], rcrowc[divsy+2], rcrowc[divsy+3], ccenter, rcleft[divsy+1], rcleft[divsy+3], rcright[divsy+1],rcright[divsy+3], pdefault, xd, yd, Nv); /* gray bars */ graybars(coff, Nhc, rcrowc[divsy+3], rcrowc[divsy+4], ccenter, rcleft[divsy+3], rcleft[divsy+4], rcright[divsy+3], rcright[divsy+4], pdefault, xd,yd,Nv); /* PAL ears */ if (colortv == 1) { palears(coff,coffr,coffl, Nhc, rcrowt, rcrowb, Nvc, divsy, -1, pdefault, xd, yd, Nv); palears(coff,coffr,coffl, Nhc, rcrowt, rcrowb, Nvc, divsy, 1, pdefault, xd, yd, Nv); } else if (colortv == 2) { ntscbars(rccoll, rccolr, divsx, rcrowt, rcrowb, divsy, -1, pdefault, xd, yd, Nv); ntscbars(rccoll, rccolr, divsx, rcrowt, rcrowb, divsy, 1, pdefault, xd, yd, Nv); ntscbars(rccoll, rccolr, divsx, rcrowt, rcrowb, divsy, -2, pdefault, xd, yd, Nv); ntscbars(rccoll, rccolr, divsx, rcrowt, rcrowb, divsy, 2, pdefault, xd, yd, Nv); } /* bottom wh - black - wh */ bottombw(round((coff[2]+coff[3])/2), Nhc, rcrowc[divsy+4], rcrowc[divsy+5], ccenter, rcleft[divsy+4], rcleft[divsy+5], rcright[divsy+4], rcright[divsy+5], pdefault, xd, yd, Nv); /* bottom yellow red circle */ bottomcirc(coff[0], Nhc, rcrowc[divsy+5], cx, cy, crad, ccenter, rcleft[divsy+5], rcright[divsy+5], pdefault, xd, yd, Nv); } /********************** set id *********************/ { /* dpi */ pair rpos=tvps(Nhc,round((rcrowc[divsy-4]+rcrowc[divsy-5])/2), xd,yd,Nv); string iRhor, iRver, ires; real Rh, Rv; Rh=Nh/xsize*inch; Rv=Nv/ysize*inch; iRhor=format("%.4gx", Rh); iRver=format("%.4gdpi", Rv); ires=insert(iRver,0, iRhor); /* size info */ int rowbot=round((rcrowc[divsy+4]+rcrowc[divsy+5])/2); pair tpos=tvps(Nhc,rowbot, xd,yd,Nv); string ihor, iver, itot, iasp, ifm; real asp, fm; ihor=format("%ix",Nh); iver=format("%i ",Nv); itot=insert(iver,0, ihor); asp=xsize/ysize; iasp=format("%.3g ",asp); fm=fs/1e6; ifm=format("%.4gMHz",fm); itot=insert(iasp,0, itot); itot=insert(ifm,0, itot); /* size of square */ int rowNsy, colNsy; pair Npos; string iNsy; pen pbw; rowNsy = round((rcrowc[divsy+5]+rcrowc[divsy+6])/2); colNsy = round((rccolc[divsx+5]+rccolc[divsx+6])/2); Npos = tvps(colNsy,rowNsy, xd,yd,Nv); iNsy = format("%i", Nsy); if (colortv>0) { pbw=pdefault+gray(1.0); } else { pbw=pdefault+gray(0.0); } label(ires, rpos, p=pbw); label(itot, tpos, p=pbw); label(iNsy, Npos, p=pbw); if (verbose > 1) write('#res:\t', ires, itot, iNsy); }