-- Read Griffin files and call GriffinFig procs
-- borrowed liberally from GriffinFile.mesa
-- M. Stone 9-Sep-81 17:52:57

DIRECTORY
GFileFormatDefs,
StyleDefs,
SplineDefs,
IOStream USING [Handle,CreateFileStream,Close,GetChar, GetBlock, SetIndex, Error],
RealConvert USING [Mesa5ToIeee],
Environment USING [charsPerPage,Byte],
PairList,
ReadGriffin,
Storage USING [Node,Free],
GriffinFig,
Convert USING [ValueToRope],
RTTypesBasic USING [NarrowFault],
Inline USING [BITAND,BITOR,LongCOPY,BytePair],
Cubic USING[CoeffsToBezier,Coeffs],
Vector USING[Vec],
Rope USING [Ref,Concat,FromString,FromChar];


ReadGriffinImpl: PROGRAM IMPORTS IOStream, Rope,
PairList, Storage, GriffinFig, SplineDefs, Inline, RealConvert, Convert, RTTypesBasic, Cubic
EXPORTS ReadGriffin =

BEGIN OPEN GFileFormatDefs, ReadGriffin;

-- ---------------------------------------------------------------------------------

Allocate: PROCEDURE [nwords: CARDINAL] RETURNS [POINTER] ← Storage.Node;
Free: PROCEDURE [POINTER TO UNSPECIFIED] ← Storage.Free;

GriffinFileNameError: PUBLIC SIGNAL[why: Rope.Ref] = CODE;
FigureNumber: TYPE = CARDINAL [1 .. 100];

fileName: Rope.Ref;
diskHandle: IOStream.Handle;
majorVersion: Environment.Byte = 1;
minorVersion: Environment.Byte = 4;
maxCPairCount: CARDINAL = 2000; -- just for safety
realConvert: BOOLEAN ← FALSE;

--from griffinfontdefs.mesa
Rot0Degrees: CARDINAL = 0;
Rot90Degrees: CARDINAL = 5400;
Rot180Degrees: CARDINAL = 10800;
Rot270Degrees: CARDINAL = 16200;

Regular: CARDINAL = 0;
Italic: CARDINAL = 1;
Bold: CARDINAL = 2;
BoldItalic: CARDINAL = Bold + Italic;

--from controllers.mesa for ComputeTexture
GreyArray: TYPE = ARRAY [0 .. 3] OF CARDINAL;
grey50: GreyArray = [52525B, 125252B, 52525B, 125252B];
greyTable: ARRAY [-1 .. 16] OF GreyArray;

--now our own datastructures
clusterMap: PairList.Relation ← NIL;
firstCluster,lastCluster: CARDINAL;
minCluster: CARDINAL ← 1;

SRHandle: TYPE = POINTER TO StyleRecord;
CRHandle: TYPE = POINTER TO ColorRecord;
Styles: SRHandle ← @StyleHead;
Colors: CRHandle ← @ColorHead;
StyleHead: StyleRecord;
ColorHead: ColorRecord;

StyleRecord: TYPE = RECORD
[next: SRHandle ← NIL,
fileStyle: CARDINAL ← 0,
textOr: REAL ← 0,
filled: BOOLEAN ← TRUE,
outlined: BOOLEAN ← TRUE,
anchor: GriffinFig.AnchorType ← left,
text: GriffinFig.TextStyleID ← 0,
fill: GriffinFig.FillStyleID ← 0,
line: GriffinFig.LineStyleID ← 0];

ColorRecord: TYPE = RECORD
[next: CRHandle ← NIL,
id: GriffinFig.ColorID ← 0,
hsb: StyleDefs.Color ← [0,0,0]];

-- --------------------------------------------------------------------------

GetReal: PROCEDURE [r: LONG UNSPECIFIED] RETURNS [out: REAL] =
BEGIN
IF realConvert THEN RETURN[RealConvert.Mesa5ToIeee[r]] ELSE RETURN[r];
END;

ReadFile: PUBLIC PROCEDURE [filename: Rope.Ref, view: ViewType] =
BEGIN ENABLE IOStream.Error => {
GriffinFileNameError["file not found"];
CONTINUE};
fileName ← NIL;
fileName ← Rope.Concat [fileName, filename];
diskHandle ← IOStream.CreateFileStream[fileName,read
! IOStream.Error => IF ec=FileNotFound THEN{
fileName ← Rope.Concat[fileName, ".Griffin"];
diskHandle ← IOStream.CreateFileStream[fileName,read];
CONTINUE}];
ReadFigure[1,view];
CloseFile[];
END;

-- ---------------------------------------------------------------------------------

CloseFile: PUBLIC PROCEDURE =
BEGIN
IOStream.Close [diskHandle];
END;


-- ---------------------------------------------------------------------------------


--all this sector moving is an artifact. When griffin files were intended to have multiple
--figures, each figure was to begin on a page boundary. The sectorIndex is just a relative
--page index
ReadFigure: PUBLIC PROCEDURE [fignum: FigureNumber, view: ViewType] =
BEGIN OPEN PairList;
fileheader: GFileHeader;
hcontrol: GFileHardcopyController;
dcontrol: GFileDisplayController;
font: GFileFont;
figurename: GFileFigureName;
stylecount, fontcount, objectcount, slen, i, f, s: CARDINAL;
refID,refFont: REF CARDINAL;
fontName,face: Rope.Ref;
fontOr: REAL;
fontMap: Relation;

ReadHeader [@fileheader];
MoveToSector [fileheader.figure [fignum]]; -- go to the right sector
ReadStructure [@figurename, lGFileFigureName];
ReadStructure [@hcontrol, lGFileHardcopyController];
ReadStructure [@dcontrol, lGFileDisplayController];
BEGIN OPEN hcontrol;
hxcenter: REAL ← GetReal[centerx];
hycenter: REAL ← GetReal[centery];
hwidth: REAL ← GetReal[width];
hheight: REAL ← GetReal[height];
pressxcenter: CARDINAL ← presscenterx;
pressycenter: CARDINAL ← presscentery;
hscale: REAL ← GetReal[scale];
--we never did make the general mechanism work. These numbers are empirical
--GriffinFig.PressController[[1,1],[1067,407]];
END;
BEGIN OPEN dcontrol;
dxcenter: CARDINAL ← centerx;
dycenter: CARDINAL ← centery;
dwidth: CARDINAL ← width;
dheight: CARDINAL ← height;
dxscale: REAL ← GetReal[xscale];
dyscale: REAL ← GetReal[yscale];
dxorigin: REAL ← GetReal[gridxo];
dyorigin: REAL ← GetReal[gridyo];
dgridsize: CARDINAL ← gridsize;
--not really a general computation...
--GriffinFig.DisplayController[[dxscale,dyscale],[0,-dheight]];
END;
--control pairs not implemented
IF dcontrol.numcontrolpairs > maxCPairCount THEN SIGNAL GriffinFileNameError["too many control pairs"];
fontcount ← ReadWord [];
fontMap ← CreateRelation[];
FOR f IN [1 .. fontcount] DO
ReadStructure [@font, lGFileFont];
slen ← LOOPHOLE[font.char[0],CARDINAL];
fontName ← NIL;
FOR i IN [1 .. slen]
DO fontName ← Rope.Concat[fontName,Rope.FromChar[font.char [i]]];
ENDLOOP;
fontOr ← (SELECT font.rotation FROM
Rot0Degrees => 0,
Rot90Degrees => 90,
Rot180Degrees => 180,
Rot270Degrees => 270,
ENDCASE => 0);
fontName ← Rope.Concat[fontName,Convert.ValueToRope[[signed[font.points]]]];
face ← (SELECT font.face FROM
Regular => "",
Bold => "B",
Italic => "I",
BoldItalic => "BI",
ENDCASE => "");
fontName ← Rope.Concat[fontName,face];
refID ← NEW[CARDINAL ← f];
refFont ← NEW[CARDINAL ← GriffinFig.Font[fontName,fontOr]];
AddPair[fontMap, refID, refFont];
ENDLOOP;
stylecount ← ReadWord [];
FOR s IN [1 .. stylecount] DO
ReadStyle[s,fontMap];
ENDLOOP;
clusterMap ← CreateRelation[];
objectcount ← ReadWord [];
THROUGH [1 .. objectcount] DO
ReadObject[view];
ENDLOOP;
PutClusters[];
DestroyRelation[fontMap];
DestroyRelation[clusterMap];
FreeStyles[];
FreeColors[];
END;



-- ---------------------------------------------------------------------------------
ReadHeader: PROCEDURE [h: POINTER TO GFileHeader] =
BEGIN
IOStream.SetIndex [diskHandle,0];
ReadStructure [h, lGFileHeader];
realConvert ← FALSE;
IF h.majversion#majorVersion
OR h.minversion#minorVersion THEN BEGIN
--version 1.4 is the first with the Ieee floating point format.
--we need to set up to convert the reals for older versions
IF h.majversion=1 AND h.minversion IN [0..3]
THEN realConvert ← TRUE
ELSE {IOStream.Close[diskHandle];
GriffinFileNameError["Incorrect file format."];
};
END;
END;
-- ---------------------------------------------------------------------------------

------



-- ---------------------------------------------------------------------------------
EqualCardinals: PairList.EqualProc = {ENABLE RTTypesBasic.NarrowFault => CONTINUE;
l,r: CARDINAL;
return ← FALSE;
--if not a cardinal, will signal out
l ← NARROW[left,REF CARDINAL]↑; r ← NARROW[right,REF CARDINAL]↑;
IF l=r THEN return ← TRUE;
};

ReadStyle: PROCEDURE [fileStyle: CARDINAL, fontMap: PairList.Relation] =
BEGIN OPEN StyleDefs;
thisstyle: Style;
fstyle: GFileStyle;
ReadStructure [@fstyle, lGFileStyle];
BEGIN OPEN thisstyle;
refFont: REF CARDINAL ← NEW[CARDINAL ← fstyle.fontid];
next ← NIL;
color ← Color[fstyle.hue, fstyle.saturation, fstyle.brightness];
width ← GetReal[fstyle.thickness];
fillcolor ← Color [fstyle.ahue, fstyle.asaturation, fstyle.abrightness];
filled ← fstyle.afilled;
outlined ← fstyle.aoutlined;
fontid ← NARROW[PairList.Right[fontMap, EqualCardinals, refFont],REF CARDINAL]↑;
backgndcolor ← Color [fstyle.bhue, fstyle.bsaturation, fstyle.bbrightness];
fillbackgnd ← fstyle.background;
anchor ← SELECT fstyle.anchor FROM
typeLeftAnchor => left,
typeCenterAnchor => center,
typeRightAnchor => right,
ENDCASE => left; -- should be an error
orientation ← SELECT fstyle.torient FROM
typeRot0 => or0,
typeRot90 => or90,
typeRot180 => or180,
typeRot270 => or270,
ENDCASE => or0; -- should be an error
END;
AddStyle[fileStyle,@thisstyle];
END;
-- ---------------------------------------------------------------------------------

-- ---------------------------------------------------------------------------------
ReadObject: PROCEDURE[view: ViewType] =
BEGIN
fileobject: GFileObject;
fcaptiontrailer: GFileCaptionTrailer;
splinetype: SplineDefs.SplineType;
nlinks: CARDINAL;
style: SRHandle;
id: GriffinFig.ObjectID;
path: GriffinFig.PathID;
pt: Vector.Vec;
captionText: Rope.Ref;
refID,refCluster: REF CARDINAL;

ReadStructure [@fileobject, lGFileObject];
style ← GetStyle[fileobject.style];
SELECT fileobject.objtype FROM
typeCurveObject, typeAreaObject => BEGIN OPEN SplineDefs;
splinetype ← SELECT fileobject.splinetype FROM
typeNUMSpline => SplineType [naturalUM],
typeCUMSpline => SplineType [cyclicUM],
typeNALSpline => SplineType [naturalAL],
typeCALSpline => SplineType [cyclicAL],
typeBEZSpline => SplineType [bezier],
typeBSISpline => SplineType [bsplineInterp],
typeBSpline => SplineType [bspline],
typeCRSpline => SplineType [crspline],
ENDCASE => SplineType [bspline];
SELECT fileobject.trajtype FROM
typeLinkedTraj => {
nlinks ← ReadWord [];
GriffinFig.StartPath[];
THROUGH [1..nlinks] DO AddLink[splinetype,FALSE] ENDLOOP;
path ← GriffinFig.EndPath[];
};
typeCSTraj => {
nlinks ← ReadWord [];
IF nlinks#1 THEN SIGNAL GriffinFileNameError["imposible cyclic spline"];
splinetype ← (SELECT splinetype FROM
naturalUM => cyclicUM,
naturalAL => cyclicAL,
ENDCASE => splinetype);
GriffinFig.StartPath[];
AddLink [splinetype,TRUE];
path ← GriffinFig.EndPath[]
};
ENDCASE => SIGNAL GriffinFileNameError["invalid trajectory"];
END;
typeCaptionObject => BEGIN
ReadStructure [@fcaptiontrailer, lGFileCaptionTrailer];
pt.x ← GetReal[fcaptiontrailer.xanchor];
pt.y ← GetReal[fcaptiontrailer.yanchor];
captionText ← ReadString [];
END;
ENDCASE => SIGNAL GriffinFileNameError["bad object"];
IF view=both OR
(view=main AND ~fileobject.hidewindow)
OR (view=alternate AND fileobject.hidewindow) THEN {
SELECT fileobject.objtype FROM
typeAreaObject => {
fillStyle: GriffinFig.FillStyleID ← (IF style.filled THEN style.fill ELSE GriffinFig.NullStyle);
lineStyle: GriffinFig.LineStyleID ← (IF style.outlined THEN style.line ELSE GriffinFig.NullStyle);
id ← GriffinFig.AreaObject[fillStyle,lineStyle,path];
};
typeCurveObject =>
id ← GriffinFig.LineObject[style.line,path];
typeCaptionObject =>
id ← GriffinFig.TextObject[style.text,captionText,style.anchor,pt,style.textOr];
ENDCASE => SIGNAL GriffinFileNameError["invalid object"];
refCluster ← NEW[CARDINAL ← fileobject.cluster];
refID ← NEW[CARDINAL ← id];
PairList.AddPair[clusterMap,refCluster,refID];
};
END;
-- ---------------------------------------------------------------------------------


-- ---------------------------------------------------------------------------------




AddLink: PROCEDURE[splinetype: SplineDefs.SplineType,cyclic: BOOLEAN] =
BEGIN OPEN SplineDefs;
knotword: GFileKnotWord;
knots: DESCRIPTOR FOR ARRAY OF SplineDefs.FPCoords;
coeffs: DESCRIPTOR FOR ARRAY OF SplineDefs.Coeffs;
num, j: CARDINAL;
fpoint: GFilePoint;
ReadStructure [@knotword, lGFileKnotWord];
num ← knotword.knotcount;
IF num=0 THEN SIGNAL GriffinFileNameError["bad link"];
SELECT knotword.knottype FROM
GFileFormatDefs.typeFD3Knot => {
knots ← DESCRIPTOR [Allocate[num*SIZE[SplineDefs.FPCoords]], num];
FOR j IN [0 .. num) DO
ReadStructure [@fpoint, lGFilePoint];
knots [j] ← SplineDefs.FPCoords[GetReal[fpoint.x], GetReal[fpoint.y]];
ENDLOOP;
IF cyclic THEN knots ← MakeCyclicKnots[knots,splinetype];
coeffs ← SplineDefs.MakeSpline[knots,splinetype];
FOR j IN [0..LENGTH[coeffs]) DO
c: Cubic.Coeffs;
c.c0 ← [coeffs[j].t0[X],coeffs[j].t0[Y]];
c.c1 ← [coeffs[j].t1[X],coeffs[j].t1[Y]];
c.c2 ← [coeffs[j].t2[X],coeffs[j].t2[Y]];
c.c3 ← [coeffs[j].t3[X],coeffs[j].t3[Y]];
GriffinFig.EnterCubic[Cubic.CoeffsToBezier[c]];
ENDLOOP;
SplineDefs.FreeCoeffs[coeffs];
Free[BASE[knots]];
};
GFileFormatDefs.typeFD0Knot, GFileFormatDefs.typeFD1Knot, GFileFormatDefs.typeFD2Knot =>
FOR j IN [0 .. num) DO
ReadStructure [@fpoint, lGFilePoint];
GriffinFig.EnterPoint[[GetReal[fpoint.x], GetReal[fpoint.y]]];
ENDLOOP;
ENDCASE => SIGNAL GriffinFileNameError["bad link"];
END;

MakeCyclicKnots: PROCEDURE[knots: DESCRIPTOR FOR ARRAY OF SplineDefs.FPCoords, type: SplineDefs.SplineType] RETURNS[DESCRIPTOR FOR ARRAY OF SplineDefs.FPCoords]=
BEGIN
cycknots: DESCRIPTOR FOR ARRAY OF SplineDefs.FPCoords;
numknots: INTEGER ← LENGTH[knots];
newLength,i: INTEGER;
IF numknots <= 0 THEN RETURN[DESCRIPTOR[NIL,0]];
SELECT type FROM
cyclicUM, cyclicAL,bezier=> newLength ← numknots+1;
bspline, crspline=> newLength ← numknots+3;
ENDCASE;
cycknots ←
DESCRIPTOR[Allocate[newLength*SIZE[SplineDefs.FPCoords]],newLength];
FOR i IN [0..numknots) DO
cycknots[i] ← knots[i];
ENDLOOP;
cycknots[numknots] ← cycknots[0];
SELECT type FROM
naturalUM=> type ← cyclicUM;
naturalAL=> type ← cyclicAL;
bspline=> BEGIN
cycknots[numknots+1] ← cycknots[1];
cycknots[numknots+2] ← cycknots[2];
END;
crspline=> BEGIN
cycknots[numknots+1] ← cycknots[1];
cycknots[numknots+2] ← cycknots[2];
END;
ENDCASE;
Free[BASE[knots]];
RETURN[cycknots];
END;


-- ---------------------------------------------------------------------------------

--style, color and object mapping stuff

--this maps a Griffin Style into calls on GriffinFig, and saves the IDs
AddStyle: PROC[fileStyle: CARDINAL, style: StyleDefs.StyleHandle] = {
sr: SRHandle ← Allocate[SIZE[StyleRecord]];
ptr: SRHandle;
color,fillcolor: GriffinFig.ColorID;
anchor: GriffinFig.AnchorType ← (SELECT style.anchor FROM
left => left, right => right, center => center, ENDCASE => ERROR);
color ← GetColor[style.color];
fillcolor ← GetColor[style.fillcolor];
sr.line ← GriffinFig.LineStyle[color,style.width];
sr.fill ← GriffinFig.FillStyle[fillcolor];
sr.text ← GriffinFig.TextStyle[color,style.fontid];
sr.fileStyle ← fileStyle;
sr.filled ← style.filled;
sr.outlined ← style.outlined;
sr.next ← NIL;
sr.textOr ← (SELECT style.orientation FROM
or0 => 0,
or90 => 90,
or180 => 180,
or270 => 270,
ENDCASE => 0);
sr.anchor ← (SELECT style.anchor FROM
left => left, right => right, center => center, ENDCASE => left);
FOR ptr ← Styles, ptr.next UNTIL ptr.next=NIL DO ENDLOOP;
ptr.next ← sr;
};

GetStyle: PROC[fileStyle: CARDINAL] RETURNS[SRHandle] = {
style: SRHandle;
FOR style ← Styles.next, style.next UNTIL style=NIL DO
IF style.fileStyle=fileStyle THEN EXIT
ENDLOOP;
IF style=NIL THEN ERROR;
RETURN[style];
};

GetColor: PROC[hsb: StyleDefs.Color] RETURNS[GriffinFig.ColorID] = {
color: CRHandle;
FOR color ← Colors.next, color.next UNTIL color=NIL DO
IF color.hsb=hsb THEN EXIT
ENDLOOP;
IF color=NIL THEN RETURN[AddColor[hsb]];
RETURN[color.id];
};

--this maps a Griffin Color into calls on GriffinFig, and saves the IDs
AddColor: PROC[hsb: StyleDefs.Color] RETURNS[GriffinFig.ColorID] = {
cr: CRHandle ← Allocate[SIZE[ColorRecord]];
ptr: CRHandle;
cr.next ← NIL;
cr.id ← GriffinFig.Color[hsb.hue,hsb.saturation,hsb.brightness,ComputeTexture[hsb]];
cr.hsb ← hsb;
FOR ptr ← Colors, ptr.next UNTIL ptr.next=NIL DO ENDLOOP;
ptr.next ← cr;
RETURN[cr.id];
};

FreeStyles: PROC = {
temp: SRHandle;
ptr: SRHandle ← Styles.next;
UNTIL ptr=NIL DO
temp ← ptr.next;
Free[ptr];
ptr ← temp;
ENDLOOP;
Styles.next ← NIL;
};

FreeColors: PROC = {
temp: CRHandle;
ptr: CRHandle ← Colors.next;
UNTIL ptr=NIL DO
temp ← ptr.next;
Free[ptr];
ptr ← temp;
ENDLOOP;
Colors.next ← NIL;
};

--cluster numbers run from firstCluster to lastCluster, but may not be
--contiguous
PutClusters: PROC = {
cluster: CARDINAL;
first: BOOLEAN ← TRUE;
do: PROC[leftPart,rightPart: REF ANY] = {
IF NARROW[leftPart,REF CARDINAL]↑=cluster THEN {
IF first THEN {GriffinFig.StartCluster[]; first ← FALSE};
GriffinFig.EnterObject[NARROW[rightPart,REF CARDINAL]↑];
};
};
FOR cluster IN [firstCluster..lastCluster] DO
first ← TRUE;
PairList.ForAllPairs[clusterMap,do];
IF ~first THEN [] ← GriffinFig.Cluster[];
ENDLOOP;
};


-- ---------------------------------------------------------------------------------


-- private low-level disk access procedures
-- ---------------------------------------------------------------------------------
ReadWord: PROCEDURE RETURNS [CARDINAL] = BEGIN
high,low: CHARACTER;
word: Inline.BytePair;
high ← IOStream.GetChar [diskHandle];
low ← IOStream.GetChar [diskHandle];
word.high ← LOOPHOLE[high]; word.low ← LOOPHOLE[low];
RETURN [LOOPHOLE[word]];
END;
-- ---------------------------------------------------------------------------------


ReadString: PROCEDURE RETURNS [r: Rope.Ref] = BEGIN
t: REF TEXT;
length: CARDINAL;
length ← LOOPHOLE[IOStream.GetChar[diskHandle], CARDINAL];
t ← NEW[TEXT[length]];
IF IOStream.GetBlock[diskHandle,t,0,length] # length
THEN SIGNAL GriffinFileNameError["Possible disk problem"];
r ← Rope.FromString[t];
--always read an even number of bytes: string length + string chars MOD 2 = 0
IF (length+1) MOD 2 # 0 THEN [] ← IOStream.GetChar[diskHandle];
END;
-- ---------------------------------------------------------------------------------


MoveToSector: PROCEDURE [si: SectorIndex] = BEGIN
IOStream.SetIndex [diskHandle, INTEGER[si*Environment.charsPerPage]];
END;



-- ---------------------------------------------------------------------------------
ReadStructure: PROCEDURE [p: LONG POINTER, l: CARDINAL] = BEGIN
from: LONG POINTER;
b: REF TEXT ← NEW[TEXT[l*2]];
IF IOStream.GetBlock [diskHandle, b, 0, 2*l] # 2*l
THEN SIGNAL GriffinFileNameError["Possible disk problem"];
from ← LOOPHOLE[b,LONG POINTER]+2;--start of bytes
Inline.LongCOPY[from,l,p];
END;
-- ---------------------------------------------------------------------------------

--compute cedargraphics texture from Griffin greys
ComputeTexture: PROC[color: StyleDefs.Color] RETURNS[texture: CARDINAL] = {OPEN Inline;
grey: INTEGER ← SELECT color FROM
--from controllers.mesa
--black-- [0,0,0] => 16,
--dk brown-- [7,255,59] => 14,
--brown-- [7,255,118] => 12,
--tan-- [0,131,217] => 4,
--maroon-- [234,255,79] => 13,
--dk red-- [0,255,160] => 11,
--red-- [0,255,255] => 8,
--orange-- [10,255,255] => 6,
--dk yellow-- [25,255,255] => 7,
--yellow-- [40,255,255] => 3,
--lt yellow-- [40,190,255] => 2,
--dk green-- [71,255,59] => 11,
--green-- [76,255,255] => 7,
--lt green-- [71,193,255] => 5,
--dk blue-- [150,255,170] => 10,
--blue-- [148,255,255] => 7,
--lt blue-- [141,150,255] => 4,
--dk aqua-- [107,255,98] => 9,
--aqua-- [107,224,255] => 5,
--cyan-- [120,255,255] => 5,
--dk purple-- [178,255,178] => 10,
--purple-- [170,224,255] => 8,
--violet-- [170,131,255] => 4,
--magenta-- [200,255,255] => 8,
--pink-- [206,170,255] => 4,
--dk grey-- [0,0,40] => 13,
--grey-- [0,0,120] => 8,
--lt grey-- [0,0,200] => 4,
--pale grey-- [0,0,230] => 1,
--white-- [0,0,255] => 0,
ENDCASE => -1;
texture ← BITAND[greyTable[grey][0],170000B];
texture ← BITOR[texture,BITAND[greyTable[grey][1],007400B]];
texture ← BITOR[texture,BITAND[greyTable[grey][2],000360B]];
texture ← BITOR[texture,BITAND[greyTable[grey][3],000017B]];
};

-- -----------------------------------------------------------------

--module code

SplineDefs.InitSplines[Allocate,Free];

greyTable [-1] ← grey50;
greyTable [0] ← [0B, 0B, 0B, 0B];
greyTable [1] ← [0B, 40100B, 0B, 2004B];
greyTable [2] ← [2004B, 40100B, 40100B, 2004B];
greyTable [3] ← [3006B, 40100B, 60140B, 2004B];
greyTable [4] ← [3006B, 60140B, 60140B, 3006B];
greyTable [5] ← [3407B, 60140B, 70160B, 3006B];
greyTable [6] ← [3407B, 160340B, 70160B, 7016B];
greyTable [7] ← [23447B, 160340B, 71162B, 7016B];
greyTable [8] ← [23447B, 162344B, 71162B, 47116B];
greyTable [9] ← [23447B, 172364B, 71162B, 47517B];
greyTable [10] ← [27475B, 172364B, 171362B, 47517B];
greyTable [11] ← [67557B, 172364B, 173366B, 47517B];
greyTable [12] ← [67557B, 173366B, 173366B, 67557B];
greyTable [13] ← [77577B, 173366B, 173767B, 67557B];
greyTable [14] ← [77577B, 177376B, 173767B, 167757B];
greyTable [15] ← [177777B, 177376B, 177777B, 167757B];
greyTable [16] ← [177777B, 177777B, 177777B, 177777B];

END.