// ScanDots.bcpl

// last modified by Ramshaw, December 11, 1980 12:42 PM
// - synch halftone screen in B as well as in S direction

// last modified by Butterfield, October 13, 1980 3:57 PM
// - ResolutionB, ResolutionS, 1X instead of 10X - 10/13/80

// errors 1600
//
//ScanDots(s,v,LeftOver)
// Called to process a BEDots entry (v is pointer).
// LeftOver is true if entry came from a LeftOver table.
//
//ScanDotsInit()
//ScanDotsClose()
// Called at beginning and end of scan conversion pass.
//
//

get "PressInternals.df"
get "PressParams.df"

// outgoing procedures
external
[
ScanDotsInit
ScanDotsClose
ScanDots
InitScreen
GCD
]

// outgoing statics
//external
//
[
//
]
//static
//
[
//
]

// incoming procedures
external
[
//FREESTORAGE
FSGet

//WINDOWS
WindowInit
WindowClose
WindowSetPosition
WindowNext
WindowReadBlock

//PRESS
PressError;PressErrorV
DblShift
FSGetX
FSPut

//PRESSML
Ugt
DoubleAdd; DoubleSub; DoubleCop
MulDiv;MulMod;DoubleAddV;DivFull;MulFull

//PUTDOTS
ScanPutDots
ScanPutHalfTone

//OS
MoveBlock
SetBlock
Zero
//FILES
FileReadPage
]

// incoming statics
external
[
ScanBuf//Core Buffer
ScanBitWc//Size of scan line
ScanBitMargin
mpSBuf//Map from scan-line to buffer
ScanS//Beginning scanline
ScanMin
ScanMax
Transparent
ResolutionS;ResolutionB//for determining halftone screen size
PressFile
ScratchFile

UseMicroCode
currentScanColor
ScanColorTable
]

// internal statics
static
[
ScanDotsWindowTable//Table of windows on dots files
nDotsWindowsToAllocate//Number of available windows
TempSLBuf//Buffer to make incoming dots contiguous
]

// File-wide structure and manifest declarations.

// Procedures

let ScanDotsInit() be
[
let a=FSGetX(nDotsRegionsB)
ScanDotsWindowTable=a
SetBlock(a,-1,nDotsRegionsB)
//-1 => available
nDotsWindowsToAllocate=-nDotsBuffers
TempSLBuf=FSGetX(lTempSL)
if UseMicroCode then
[ @ScanPutDots=ScanPutDotsTrap
@ScanPutHalfTone=ScanPutDotsTrap
]
]

and ScanDotsClose() be
[
FSPut(ScanDotsWindowTable)
]

and ScanDots(v,LeftOver) = valof
[ if (currentScanColor eq 255)&
((v>>BEDots.Code ne 0)%//never print halftones if color setting is white
Transparent) //print white bitmaps unless Transparent
then resultis 0//do nothing
let S=0
let SLWC=v>>BEDots.BitFileSLWC
let ScreenAngle=v>>BEDots.ScreenAngle
let ScreenLPI=v>>BEDots.ScreenModulus
//Lines per inch
let ScreenMagnitude=v>>BEDots.ScreenAmplitude

//Do first-time processing, including allocating a slot for window entry
unless LeftOver then
[
//First time!
v>>BEDots.Scount=DotsDeltaConst
//Initial phase
S=(v>>BEDots.Sstart)&(BANDWidth-1)

let avail=-1
for i=0 to nDotsRegionsB-1 do
if ScanDotsWindowTable!i eq -1 then
[
avail=i
break
]
if avail eq -1 then [ PressError(1600); resultis 0 ]
//No more!
ScanDotsWindowTable!avail=0
//No present window
v>>BEDots.WindowNumber=avail

if v>>BEDots.Code ne 0 then
//grab error distribution buffer, etc.
[ test v>>BEDots.ScreenStart then
//user specified dot
[ let scratch=vec 1024
FileReadPage(ScratchFile,v>>BEDots.ScreenStart,scratch)
let nCells=scratch!0
let nLines=scratch!1
let nShifts=scratch!2
let sDef=FSGet(3+nCells+nCells/nLines)
if sDef eq 0 then [ PressError(1620);resultis 0]
MoveBlock(sDef,scratch,3+nCells)
//store in BEDots vector
v>>BEDots.ScreenStart=sDef
v>>BEDots.ScreenAddress=sDef+3+nCells
v>>BEDots.ScreenModulus=nCells/nLines
//and rescale for funny press values
let scale=v>>BEDots.IMax-v>>BEDots.IMin
let half=scale*2
for i=3 to nCells+3 do sDef!i=((scale-sDef!i)*4)-half
]
or //custom made while you wait
if ScreenLPI ne 0 then
unless InitScreen(v) do [ PressError(1620);resultis 0]
//let errorbuffsize=(-v>>BEDots.Bdim)+1
//let errorptr=FSGet(errorbuffsize)
//if errorptr eq 0 then//not enough room
// [ PressError(1620);resultis 0]
//v>>BEDots.ErrorBuffer=errorptr
//let thresh=v>>BEDots.IMax-v>>BEDots.IMin
//SetBlock(errorptr,thresh,errorbuffsize)
]

if SLWC gr lTempSL then [ PressError(1621); resultis 0 ]
]

//Find the window for this dots region
let wi=v>>BEDots.WindowNumber
let w=ScanDotsWindowTable!wi
unless w then
[
test nDotsWindowsToAllocate eq 0 then
[//Must kick one out.
for i=0 to nDotsRegionsB-1 do
[
let s=ScanDotsWindowTable!i
if s ne -1 & s ne 0 then
[
WindowClose(s)
ScanDotsWindowTable!i=0//Kicked out
break
]
]
] or nDotsWindowsToAllocate=nDotsWindowsToAllocate+1
let f=v>>BEDots.File
if (f le FILEPress)&(f ge 0) then f=((f eq FILEPress)? PressFile,ScratchFile)
//Get two pages of buffering here because a typical scan-line will cross
// a page boundary. Because we are almost always blowing the picture
// up, we will backtrack a lot. Extra buffer will prevent another disk
// reference. Note that someday this might be a good place to put
// PageAhead and PageBack strategies, depending on the direction we
// are going in the file!
w=WindowInit(f,2)
ScanDotsWindowTable!wi=w
]

let Sdim=v>>BEDots.Sdim
//- # scan lines to go
let Smagnify=v>>BEDots.Smagnify
let Scount=v>>BEDots.Scount
let PutDots=nil
test v>>BEDots.Code eq 0
ifso PutDots=(currentScanColor eq 0)?ScanPutDots,ScanPutShadedDots
ifnot PutDots=ScanPutHalfTone
let bufferPosChanged=true
[
[
if S eq BANDWidth then
[//More to go.
v>>BEDots.Sdim=Sdim
v>>BEDots.Scount=Scount
resultis size BEDots/16//Leave leftover for next time
]

if bufferPosChanged then
[//May need to update buffer info
let wp=vec 1
DoubleCop(wp,lv v>>BEDots.BitFilePos)//Current position
v>>BEDots.BitPhase=(wp!1)&(#17)//Position in word
DblShift(wp,4)//Word position

WindowSetPosition(w,wp)//Reset input line
test -w>>W.Offset ge SLWC
ifso v>>BEDots.BitBuf=w>>W.Base+w>>W.Offset
ifnot
[
WindowReadBlock(w, TempSLBuf, SLWC)
v>>BEDots.BitBuf=TempSLBuf
]
bufferPosChanged=false
]

//if halftone, set up screen
let mod=v>>BEDots.ScreenModulus
unless mod eq 0 do
[ let sDef=v>>BEDots.ScreenStart
let A=sDef!0
let p=sDef!1
let D=sDef!2
let Base=sDef+3+mod*((ScanS+S) rem p)
let firstElement=(MulMod((ScanS+S)/p,D,mod)+(v>>BEDots.Bstart)) rem mod
let thisScreen=v>>BEDots.ScreenAddress
MoveBlock(thisScreen,Base+firstElement,mod-firstElement)
MoveBlock(thisScreen+mod-firstElement,Base,firstElement)
]

//if opaque, zero appropriate output bit range
let Bdim=-(v>>BEDots.Bdim) // # bits to output
let Bstart=v>>BEDots.Bstart
if v>>BEDots.Bincr ls 0 then Bstart=Bstart-Bdim+1 //"start" at bottom
let Bend=Bstart+Bdim-1

if v>>BEDots.Opaque then
[
let BaseAddr=mpSBuf!S
let r1word=Bstart rshift 4
let r2word=Bend rshift 4
let r1mask=-1 lshift (#20-(Bstart))
let r2mask=-1 rshift ((Bend)+1)
BaseAddr!r1word=BaseAddr!r1word&r1mask
if r2word ne r1word then Zero(BaseAddr+r1word+1,(r2word-r1word)-1)
BaseAddr!r2word=BaseAddr!r2word&r2mask
]
if Bstart ls ScanMin then ScanMin=Bstart
if Bstart gr ScanMax then ScanMax=Bstart
if Bend ls ScanMin then ScanMin=Bend
if Bend gr ScanMax then ScanMax=Bend


//Now do all the work:
PutDots(S,v)
//Go do a scan line

Sdim=Sdim+1
if Sdim eq 0 then
[//End of bits region
WindowClose(w)//Delete the window.
nDotsWindowsToAllocate=nDotsWindowsToAllocate-1
ScanDotsWindowTable!wi=-1//Available
//if v>>BEDots.Code ne 0 then FSPut(v>>BEDots.ErrorBuffer)
if v>>BEDots.ScreenModulus ne 0 then FSPut(v>>BEDots.ScreenStart)
resultis 0//No leftovers
]
S=S+1

unless Smagnify then break
//If reducing, only display once.
Scount=Scount-v>>BEDots.Sdelta
if Scount ls 0 then
[
Scount=Scount+DotsDeltaConst
break
]
] repeat

[
DoubleAdd(lv v>>BEDots.BitFilePos,lv v>>BEDots.BitFileInc) //Get a bit
bufferPosChanged=true
if Smagnify then break
Scount=Scount-v>>BEDots.Sdelta
if Scount ls 0 then
[
Scount=Scount+DotsDeltaConst
break
]
] repeat

] repeat
//Many,many scan lines!

]

and ScanPutShadedDots(S,v) be
[ let thisS=ScanS+S
let savedSBuf=mpSBuf!S
let tempSBuf=FSGet(ScanBitWc+1)
//+1 because we’re off by one somewhere
// in computing scan length
if tempSBuf eq 0 then [ ScanPutDots(S,v);return]
Zero(tempSBuf,ScanBitWc+1)
tempSBuf=tempSBuf-ScanBitMargin/16
mpSBuf!S=tempSBuf
ScanPutDots(S,v)
mpSBuf!S=savedSBuf
//and now, update color
let cycleLen=ScanColorTable!0
let cyclePerBlock=ScanColorTable!1
let linesPerBlock=ScanColorTable!2
let blockNum=thisS/linesPerBlock
let lineNum=thisS rem linesPerBlock
let colorLine=ScanColorTable+3+lineNum*cycleLen
let lineIndex=(blockNum*cyclePerBlock+(ScanMin rshift 4)) rem cycleLen
test Transparent then
for i=ScanMin rshift 4 to ScanMax rshift 4 do
[ savedSBuf!i=savedSBuf!i%(tempSBuf!i&(colorLine!lineIndex))
lineIndex=lineIndex+1
if lineIndex eq cycleLen then lineIndex=0
]
or //solid
for i=ScanMin rshift 4 to ScanMax rshift 4 do
[ let bitsWord=tempSBuf!i
savedSBuf!i=
(savedSBuf!i&(not bitsWord))%(bitsWord&(colorLine!lineIndex))
lineIndex=lineIndex+1
if lineIndex eq cycleLen then lineIndex=0
]

FSPut(tempSBuf+ScanBitMargin/16)
]

//InitScreen(ptr)
// initialize halftone screen (returns false if no room left)
// v is a pointer to a BEDots entry, containing
//
LinesPerInch in ScreenModulus
//
Angle in ScreenAngle
// the screen rotation algorithm was inspired by Thomas M. Holladay’s
//
internal report "Creation of Variable Angles of Dot Screens for
//
Application in Serial Halftoning Techniques."

and InitScreen(v) = valof
[ //first, initialize some tables for calculating xrot,yrot from LPI,ang

//here is Sqrt(1+theta**2) for theta=0 to 89
//entries are multiplies by 1000
//xrot=Resolution/(Sqrt(1+theta**2)*LinesPerInch)
let Sqrt1PlusThetaSq=table
[ 1000;1000;1001;1001;1002
1004;1006;1007;1010;1012
1015;1019;1022;1026;1031
1035;1040;1046;1051;1058
1064;1071;1079;1086;1095
1103;1113;1122;1133;1143
1155;1167;1179;1192;1206
1221;1236;1252;1269;1287
1305;1325;1346;1367;1390
1414
]
//now, we set up a table of tangents
//again, entries are multiplied by 1000
//yrot=xrot*Tan(theta)
let Tan=table
[ 0000;0017;0035;0052;0070
0088;0105;0123;0141;0158
0176;0194;0213;0231;0249
0268;0287;0306;0325;0344
0364;0384;0404;0424;0445
0466;0488;0510;0532;0554
0577;0601;0625;0650;0675
0700;0727;0754;0781;0810
0839;0869;0900;0933;0966
1000
]

let LPI=v>>BEDots.ScreenModulus
let theta=v>>BEDots.ScreenAngle;if theta gr 45 then theta=90-theta
if ResolutionS ne ResolutionB then PressErrorV(1624,ResolutionS)
let t1 = MulDiv(ResolutionS, 100, LPI);
let xrot=MulDiv(t1,10,Sqrt1PlusThetaSq!theta)
let yrot=MulDiv(xrot,Tan!theta,1000)
if v>>BEDots.ScreenAngle gr 45 then //swap xrot, yrot
[ let t=xrot;xrot=yrot;yrot=t]

let xyMat=MakeXYMatrix(xrot,yrot)
//0’s on unused spots,
//good stuff elsewhere
if xyMat eq 0 then resultis false
//no room

//now, do the magic transformation
let A=xrot*xrot+yrot*yrot
//Area of halftone dot
let p=GCD(xrot,yrot)
//p is # of repeating strings
//each string is of length A/p
//since p|x and p|y, clearly p|(x**2+y**2)
//to calculate D, the horizontal shift, we have two modular equations:
// (x/p)*D=(mod L) -y
!!BUG: was y
// (y/p)*D=(mod L) x
!!BUG: was -x
//the fastest way to solve for is
// 1) find k (0 le k ls (x/p)) such that (y+kL) rem (x/p) eq 0
//
(that is, D=(y+kL)/(x/p) is a solution to the first equation)
// 2) test that choice of D in the second equation: if it works,
//
we’re done
//note that this takes at most x/p trials (and typically x/p ls 20)
// even though there are L <=(x**2+y**2)/p> possible values for D
let D=valof

[ if yrot eq 0 then resultis 0//without this test, we need to have
//xrot rem L in the eq statement
let L=A/p
let xp=xrot/p
let yp=yrot/p
for k=0 to yp-1 do
if ((xrot+MulMod(k,L,yp)) rem yp) eq 0 then//part 1 OK
[ let dv=vec 1
MulFull(k,L,dv)
DoubleAddV(dv,xrot)
let d=DivFull(dv,yp)
if MulMod(xp,d,L) eq (L-yrot) then resultis d
]
PressError(1622)//never happens
]

//the format for a screen definition is:
// Header (3 wds) A,p,D
// string of A elements (permanent)
// A/p blanks in which to store appropriate screen for each line
//note ScreenModulus=A/p
let sDef=FSGet(3+A+A/p)
if sDef eq 0 then resultis false
//no room
sDef!0=A
sDef!1=p
sDef!2=D

//re-form xyMat into the linear repeating sequence
let i=3
//index into sDef
let side=xrot+yrot
//size of xyMat

let initOff=valof
[ for k=0 to side-1 do if xyMat!k gr 0 then resultis k
resultis 0
]

for z=0 to p-1 do
//there are p repeating strings
[ let j=z
let off=initOff
[ //tack on elements from rvec line j
let xyPtr=xyMat+j*side
for k=off to side-1 do
if xyPtr!k gr 0 then
[ sDef!i=xyPtr!k;i=i+1]
j=(j+yrot) rem side
off=0
] repeatuntil j eq z
let xyPtr=xyMat+j*side
for k=0 to initOff-1 do
if xyPtr!k gr 0 then [ sDef!i=xyPtr!k;i=i+1]
]
unless i eq (A+3) do PressError(1623)
//never happens

//update sequence so:
// 1) sum=0
// 2) scaled for IMin,IMax
let even=(A&1) eq 0
let scale=(A+1)/2
let black=v>>BEDots.IMin
let range=MulDiv(v>>BEDots.IMax-black,v>>BEDots.ScreenAmplitude,50)-1
for i=3 to A+2 do
[ sDef!i=sDef!i-scale
if even&(sDef!i le 0) then sDef!i=sDef!i-1
test sDef!i ge 0 then sDef!i=MulDiv(range,sDef!i,scale)+black
or sDef!i=-MulDiv(range,-(sDef!i),scale)+black
]

//store in BEDots vector
v>>BEDots.ScreenStart=sDef
v>>BEDots.ScreenAddress=sDef+3+A
v>>BEDots.ScreenModulus=A/p

//free up the temporary storage used
FSPut(xyMat)
resultis true
]

//Euclidean algorithm, Knuth vol. 2, p297
and GCD(u,v)=valof
[ if v eq 0 then resultis u
let r=u rem v
u=v
v=r
] repeat

and Min(a,b)=(a ls b)?a,b
and Max(a,b)=(a gr b)?a,b

and MakeXYMatrix(x,y)=valof
[ let side=x+y
let rvec=FSGet(side*side+1)
if rvec eq 0 then resultis 0
//no room

//set up middle section (all active)
let a=(y gr x)?(y-x),(x-y)
let z=Min(x,y)
for j=z to z+a-1 do
for i=z to z+a-1 do
rvec!(j*side+i)=32000

//now, set up surrounding rectangles
let Avec=FSGet(x*y+1)
if Avec eq 0 then [ FSPut(rvec);resultis 0]

//first, set the rectangle elements to on or off (32000,-32000)
for i=0 to x-1 do
[
let area=((i*2+1)*y+x)/(x*2)//((i*y+(i+1)*y+x))/(2*x)
let sep=y-area
for j=0 to sep-1 do [ Avec!(j*x+i)=-32000]//OFF
for j=sep to y-1 do [ Avec!(j*x+i)=32000]//ON
]

//rotate 90 degrees for side elements
let Bvec=FSGet(x*y+1)
if Bvec eq 0 then [ FSPut(Avec);FSPut(rvec);resultis 0]
for i=0 to x-1 do
for j=0 to y-1 do
[ let roti=(y-1)-j
let rotj=i
Bvec!(roti+rotj*y)=Avec!(i+j*x)
]

//schlong the appropriate rectangles in their places in rvec
Store(Avec,rvec,x,y,true)
//store it starting at (0,0)
Store(Avec,rvec+x*side+y,x,y,false)
//start at (y,x)
Store(Bvec,rvec+x,y,x,true)
//start at (x,0)
Store(Bvec,rvec+y*side,y,x,false)
//start at (0,y)

FSPut(Avec)
FSPut(Bvec)

//OK, all "active" squares in rvec are set to 32000
//now, set the active guys to appropriate counter values
let off=side/2
let ylim=(3*side)/2
//1.5 > sqrt(2)
let counter=1
let slim=side-1

for yt=0 to ylim do
[ let i=0
let j=yt
let yt1sq=(yt+1)*(yt+1)
[ //for the eight points (i,j),(slim-i,j),(i,slim-j),(slim-i,slim-j),
// (j,i),(slim-j,i),(j,slim-i),(slim-j,slim-i) do:
// if point eq "active" (32000) then set it to counter
Set(rvec,off+j,off+i,side,lv counter)
Set(rvec,off+i,off+j,side,lv counter)
Set(rvec,slim-(off+i),off+j,side,lv counter)
Set(rvec,slim-(off+j),off+i,side,lv counter)
Set(rvec,slim-(off+j),slim-(off+i),side,lv counter)
Set(rvec,slim-(off+i),slim-(off+j),side,lv counter)
Set(rvec,off+i,slim-(off+j),side,lv counter)
Set(rvec,off+j,slim-(off+i),side,lv counter)

i=i+1
if (i*i+j*j) ge yt1sq then [ j=j-1;i=i-1]
] repeatuntil j ls i
]

resultis rvec
]

and Store(inVec,destVec,xlen,ylen,pos) be
[ let side=xlen+ylen
//size of destination vector
for j=0 to ylen-1 do
for i=0 to xlen-1 do
[ let z=inVec!(j*xlen+i)
unless pos do z=-z
if z ls 0 then z=0
destVec!(j*side+i)=z
]
]

and Set(v,i,j,side,counterlv) be
[ if (i ge side)%(j ge side)%(i ls 0)%(j ls 0) then return
let p=v+i+j*side
let counter=@counterlv
if @p eq 32000 then [ @p=counter;@counterlv=counter+1]
]