AISAnimationImpl.mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
Last Edited by: Crow, March 31, 1989 11:23:33 am PST
Bloomenthal, September 26, 1988 12:04:43 pm PDT
DIRECTORY
Atom      USING [ GetPropFromList, PutPropOnList, RemPropFromList ],
CedarProcess    USING [ CheckAbort, DoWithPriority ],
Rope      USING [ Cat, Equal, ROPE, Substr, Length ],
BasicTime    USING [ Now ],
Convert     USING [ RopeFromInt, RopeFromTime ],
Real      USING [ Round ],
FS       USING [ ExpandName, ComponentPositions, FileInfo, Error,
          StreamOpen ],
IO       USING [ Close, GetChar, GetRopeLiteral, GetInt, EndOf, EndOfStream,
          PutChar, PutRope, STREAM ],
Terminal     USING [ Virtual, Current, WaitForBWVerticalRetrace ],
ImagerBackdoor   USING [ AccessBufferRectangle ],
ImagerPixel    USING [ PixelMap, MakePixelMap ],
ImagerSample   USING [ BasicTransfer, Clip, GetBox, GetSamples, NewSampleMap,
          NewSamples, PutSamples, SampleBuffer, SampleMap,
          Transfer ],
SF       USING [ SizeF, SizeS ],
ConvertRasterObject  USING [ AISFromSampleMap, SampleMapFromAIS ],
ThreeDBasics   USING [ Box, Context, Error, ImagerProc, ImagerProcRec, Pair, IntegerPair
         ],
SceneUtilities   USING [ PrependWorkingDirectory ],
SurfaceRender   USING [ ValidateContext ],
RenderWithPixels  USING [ AllocatePixelMemory ],
ColorDisplayRender  USING [ StuffBuf ],
AISAnimation   USING [ FrameSequence ];
AISAnimationImpl: CEDAR PROGRAM
IMPORTS Atom, BasicTime, CedarProcess, ColorDisplayRender, Convert, ConvertRasterObject, FS, ImagerBackdoor, ImagerPixel, ImagerSample, IO, Real, RenderWithPixels, Rope, SceneUtilities, SF, SurfaceRender, Terminal, ThreeDBasics
EXPORTS AISAnimation
~ BEGIN
Internal Declarations
Context: TYPE ~ ThreeDBasics.Context;
Box: TYPE ~ ThreeDBasics.Box;
Pair: TYPE ~ ThreeDBasics.Pair;
IntegerPair: TYPE ~ ThreeDBasics.IntegerPair;
ImagerProc: TYPE ~ ThreeDBasics.ImagerProc;
ImagerProcRec: TYPE ~ ThreeDBasics.ImagerProcRec;
RopeSequence: TYPE ~ RECORD[ SEQUENCE length: NAT OF Rope.ROPE ];
FrameSequence: TYPE ~ AISAnimation.FrameSequence;
PixelMap: TYPE ~ ImagerPixel.PixelMap;
SampleMap: TYPE ~ ImagerSample.SampleMap;
ROPE: TYPE ~ Rope.ROPE;
LORA: TYPE ~ LIST OF REF ANY;
bitsPer75thSecond: INT ~ 314573;  -- roughly what bitBlt can move in one LF field time
labelsEnabled: BOOLEANFALSE;
labelPosn: Pair ← [0.02, 0.02];
Procedures for Animation
PlayBackNumberedAISFiles: PUBLIC PROC[context: REF Context,
             fileRoot : Rope.ROPE,
         startNum, numFiles, framesPerSec, secondsPlayingTime : NAT] ~ {
frames: REF FrameSequence ← CacheNumberedAISFiles[context, fileRoot, numFiles, startNum];
PlayBackAISCache[context, frames, framesPerSec, secondsPlayingTime];
};
StoreFiles: PUBLIC PROC[context: REF Context, fileRoot: Rope.ROPE, number: NATLAST[NAT]] ~ {
Update log file, if animation, then store image
cp: FS.ComponentPositions; fullFName, ext: Rope.ROPE;
IF number # LAST[NAT] THEN {
log: IO.STREAM ;
log ← FS.StreamOpen[
fileName: GetLogFileName[ SceneUtilities.PrependWorkingDirectory[context, fileRoot] ],
accessOptions: $append,
wDir: NARROW[ Atom.GetPropFromList[context.props, $WDir] ]
];
log.PutRope[ Rope.Cat[
" Frame no. ", Convert.RopeFromInt[number], " written at ",
Convert.RopeFromTime[from: BasicTime.Now[], start: months, end: seconds], "\n"
] ];
IO.Close[log];
};
[fullFName, cp, ] ← FS.ExpandName[fileRoot];  -- is this an RGB file name?
ext ← Rope.Substr[ fullFName, cp.ext.start, cp.ext.length];
IF number # LAST[NAT]
THEN fullFName ← PasteInSequenceNo[
SceneUtilities.PrependWorkingDirectory[context, fileRoot],
number
]
ELSE fullFName ← SceneUtilities.PrependWorkingDirectory[context, fileRoot];
IF Rope.Equal[ext, "rgb", FALSE]
THEN PutRGB[ context, fullFName ]
ELSE PutAIS[ context, fullFName ];
};
CacheAISFiles: PUBLIC PROC[context: REF Context, fileOfNames: Rope.ROPE,
         frames: REF FrameSequence ← NIL]
     RETURNS[REF FrameSequence] ~ {
pictureNames: REF RopeSequence;
GetName: PROC[number: NAT] RETURNS[Rope.ROPE] ~ {
RETURN[ pictureNames[number] ];
};
Extend: PROC[names: REF RopeSequence] ~ {
newNames: REF RopeSequence ← NEW[ RopeSequence[names.length*2] ];
FOR i: NAT IN [0..names.length) DO newNames[i] ← names[i]; ENDLOOP;
names ← newNames;
};
input: IO.STREAM ← FS.StreamOpen[ fileName: fileOfNames ];
numFiles: NATIO.GetInt[input];    -- first thing in file is number of pictures
pictureNames ← NEW[ RopeSequence[numFiles] ];
numFiles ← 0;
WHILE NOT IO.EndOf[input] DO   -- read robustly in case number is wrong
IF numFiles >= pictureNames.length THEN Extend[pictureNames];
pictureNames[numFiles] ← IO.GetRopeLiteral[input ! IO.EndOfStream => LOOP];
IF pictureNames[numFiles] # NIL THEN numFiles ← numFiles + 1;
ENDLOOP;
RETURN[ CacheFiles[context, numFiles, 0, GetName, frames] ];
};
CacheNumberedAISFiles: PUBLIC PROC[context: REF Context,
            fileRoot: Rope.ROPE, numFiles: NAT, start: NAT ← 0]
        RETURNS[REF FrameSequence] ~ {
GetName: PROC[number: NAT] RETURNS[Rope.ROPE] ~ {
RETURN[ PasteInSequenceNo[
SceneUtilities.PrependWorkingDirectory[context, fileRoot],
number
] ];
};
RETURN[ CacheFiles[context, numFiles, start, GetName, NIL] ];
};
CacheFiles: PROC[context: REF Context, numFiles, start: NAT ← 0,
      getname: PROC[number: NAT] RETURNS[Rope.ROPE],
      frames: REF FrameSequence ← NIL ]
    RETURNS[REF FrameSequence] ~ {
Action: PROC ~ {
frameFailed, firstFrameFound: BOOLEANFALSE;
firstFrame, i: NAT ← start;
tmpSamples: ARRAY[0..5) OF SampleMap ← ALL[NIL];
WHILE TRUE DO
IF context.stopMe^ THEN RETURN[];  -- shut down if stop signal received
CedarProcess.CheckAbort[];      -- respond to Stop button
RenderWithPixels.AllocatePixelMemory[ context ]; -- get new set of pixels each frame
[] ← GetAIS[
context, getname[i]
! ThreeDBasics.Error => IF reason.code = $NotFound
THEN { frameFailed ← TRUE; CONTINUE; }
];
IF NOT frameFailed
THEN firstFrameFound ← TRUE   -- found frame, legit sequence started
ELSE IF NOT firstFrameFound  -- frame failed, has a sequence been started?
THEN IF i < start + 16      -- try for 16 consecutive frames, just in case
THEN { firstFrame ← i ← i + 1; frameFailed ← FALSE; LOOP; }
ELSE EXIT      -- frame not found after 16 tries, assume bad name
ELSE EXIT;  -- must be end of sequence, earlier frames found but not this one
IF frames = NIL THEN frames ← NEW[ FrameSequence[numFiles] ];
IF frames.maxLength <= i - firstFrame THEN { -- extend FrameSequence if necessary
newFrames: REF FrameSequence ← NEW[ FrameSequence[frames.maxLength+16] ];
FOR j: NAT IN [0..frames.maxLength) DO newFrames[j] ← frames[j]; ENDLOOP;
frames ← newFrames;
};
Get pixels left in proplist by GetAIS
frames[i - firstFrame] ← NARROW[ Atom.GetPropFromList[context.props, $TempPixels] ];
IF labelsEnabled AND context.viewer # NIL  -- pull pixels out of viewer to get label
THEN FOR k: NAT IN [0..frames[i - firstFrame].samplesPerPixel) DO
context.class.drawInViewer[
context,
NEW[ImagerProcRec ← [
GetPixelsFromViewer, LIST[ NEW[NAT ← k], frames[i - firstFrame][k] ]
]]
];
ENDLOOP;
context.props ← Atom.RemPropFromList[context.props, $TempPixels]; -- save VM
frames.length ← i - firstFrame + 1;
i ← i + 1;
IF numFiles > 0 AND i - start >= numFiles THEN EXIT; -- found requested no. of files
ENDLOOP;
IF NOT firstFrameFound THEN {
frames ← NIL;      -- no frames found, prevent further mistakes
SIGNAL ThreeDBasics.Error[[$NotFound, "File not found by that name"]];
};
};
CedarProcess.DoWithPriority[background, Action];
IF context.stopMe^ THEN RETURN[NIL] ELSE RETURN[frames];
};
GetPixelsFromViewer: ImagerProc ~ {
PROC[ context: REF Context, imagerCtx: Imager.Context, data: REF ANYNIL ];
DoGetPixels: PROC[pixelMap: PixelMap] ~ {
list: LORANARROW[data];
i: NATNARROW[list.first, REF NAT]^;
TransferSamples[
dstMap: NARROW[list.rest.first],
srcMap: pixelMap[i],
xOffset: 0, yOffset: 0
];
};
ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoGetPixels, context.viewPort^];
};
PlayBackAISCache: PUBLIC PROC[context: REF Context,
           frames: REF FrameSequence,
           framesPerSec, secondsPlayingTime: NAT,
           bothWays: BOOLEANFALSE, startNum: NAT ← 0] ~ {
vt: Terminal.Virtualcontext.terminal;
waitCount, transferTime: NAT;
repetitions: INTMAX[1,
       INT[secondsPlayingTime] * framesPerSec / (frames.length-startNum)];
IF frames = NIL THEN {
SIGNAL ThreeDBasics.Error[[$MisMatch, "NoCachedFrames"]]; RETURN;
};
context.stopMe^ ← FALSE;       -- get stop flag unstuck, if necessary
context.imageReady ← FALSE;    -- discourage use of bits by other processes
context.class.validateDisplay[context];     -- update viewport, etc.
[waitCount, transferTime] ← GetWaitCount[context, framesPerSec];
IF waitCount = 0
THEN repetitions ← (secondsPlayingTime * 75 / transferTime) / (frames.length - startNum);
IF bothWays THEN repetitions ← repetitions/2;
FOR i: INT IN [0..repetitions) DO        -- play back
FOR j: NAT IN [startNum..frames.length) DO
FOR k: NAT IN [0..waitCount) DO vt.WaitForBWVerticalRetrace[]; ENDLOOP;
frames.currentFrame ← j;
IF context.stopMe^ THEN RETURN[];  -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
ShowAISCacheFrame[context, frames];
ENDLOOP;
IF bothWays THEN FOR j: NAT DECREASING IN [startNum..frames.length) DO
FOR k: NAT IN [0..waitCount) DO vt.WaitForBWVerticalRetrace[]; ENDLOOP;
frames.currentFrame ← j;
IF context.stopMe^ THEN RETURN[];  -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
ShowAISCacheFrame[context, frames];
ENDLOOP;
ENDLOOP;
context.imageReady ← TRUE;    -- encourage use of bits by other processes
};
ShowNextAISCacheFrame: PUBLIC PROC[context: REF Context,
           frames: REF FrameSequence, framesPerSec: INTEGER] ~ {
waitCount: NAT;
vt: Terminal.VirtualTerminal.Current[];
IF framesPerSec < 0
THEN frames.currentFrame ← (frames.currentFrame + frames.length-1) MOD frames.length
ELSE frames.currentFrame ← (frames.currentFrame + 1) MOD frames.length;
[waitCount: waitCount] ← GetWaitCount[context, ABS[framesPerSec]];
FOR k: NAT IN [0..waitCount) DO vt.WaitForBWVerticalRetrace[]; ENDLOOP;
IF context.stopMe^ THEN RETURN[];  -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
ShowAISCacheFrame[context, frames];
};
Support Procedures
Ceiling: PROC[number: REAL] RETURNS[result: INTEGER] ~ {
result ← Real.Round[number];
IF result < number THEN result ← result + 1;
};
GetLogFileName: PUBLIC PROC[fileRoot: Rope.ROPE] RETURNS[Rope.ROPE] ~ {
cp: FS.ComponentPositions; fullFName, fName: Rope.ROPE;
[fullFName, cp, ] ← FS.ExpandName[fileRoot];
IF cp.ext.length = 0
THEN fName ← Rope.Substr[ fullFName, 0, cp.ext.start]  -- name for filling in numbers
ELSE fName ← Rope.Substr[ fullFName, 0, cp.ext.start-1];  -- drop "." before ext
RETURN[ Rope.Cat[fName, ".", "log"] ];
};
PasteInSequenceNo: PUBLIC PROC[fileRoot: Rope.ROPE, number: NAT] RETURNS[Rope.ROPE] ~ {
num: Rope.ROPE ← Convert.RopeFromInt[number, 10, FALSE];
SELECT Rope.Length[num] FROM
1 => num ← Rope.Cat["00", num];
2 => num ← Rope.Cat["0", num];
3 => {};
ENDCASE => SIGNAL ThreeDBasics.Error[[$MisMatch, "Sequence # out of range"]];
RETURN[ PasteInLabel[fileRoot, num ] ];
};
PasteInLabel: PROC[fileRoot: Rope.ROPE, label: Rope.ROPE] RETURNS[Rope.ROPE] ~ {
cp: FS.ComponentPositions; fullFName, fName, ext: Rope.ROPE;
[fullFName, cp, ] ← FS.ExpandName[fileRoot];
IF cp.ext.length = 0
THEN {
fName ← Rope.Substr[ fullFName, 0, cp.ext.start];  -- name for filling in numbers
ext ← "ais";
}
ELSE {
fName ← Rope.Substr[ fullFName, 0, cp.ext.start-1];  -- drop "." before ext
ext ← Rope.Substr[ fullFName, cp.ext.start, cp.ext.length];
};
RETURN[ Rope.Cat[fName, label, ".", ext] ];
};
GetWaitCount: PROC[context: REF Context, framesPerSec: NAT]
     RETURNS[waitCount: NAT, transferTime: NAT] ~ {
width: INT ← Ceiling[context.viewPort.w];
height: INT ← Ceiling[context.viewPort.h];
addOn: NATIF context.antiAliasing THEN 1 ELSE 0;
bitsPerPixel: NATIF context.class.displayType = $FullColor THEN 24 ELSE 8;
transferTime ← width * height * bitsPerPixel / bitsPer75thSecond;
IF framesPerSec = 0
THEN waitCount ← 150
ELSE waitCount ← 75 / framesPerSec;  -- LF display VerticalRetrace comes 75/sec.
IF transferTime > waitCount   -- take bitBlt overhead into account
THEN waitCount ← 0
ELSE waitCount ← waitCount - transferTime;
waitCount ← MAX[waitCount, 1];
};
ShowAISCacheFrame: PUBLIC PROC[ context: REF Context,
           frames: REF FrameSequence ] ~ {
IF frames.currentFrame < 0 OR frames.currentFrame >= frames.length
THEN SIGNAL ThreeDBasics.Error[[$MisMatch, "Frame number out of range"]];
IF frames[frames.currentFrame] # NIL
THEN IF context.viewer # NIL
THEN {
context.class.updateViewer[context];
context.pixels ← frames[frames.currentFrame];
context.class.drawInViewer[
context, NEW[ImagerProcRec ← [ColorDisplayRender.StuffBuf, context]]
];
}
ELSE {
samplesPerColor: NATIF context.class.displayType = $FullColor THEN 3 ELSE 1;
FOR i: NAT IN [0..samplesPerColor) DO
ImagerSample.Transfer[dst: context.pixels[i], src: frames[frames.currentFrame][i]];
ENDLOOP;
};
};
standardNames: ARRAY[0..6) OF RECORD[ pref, alt, other: Rope.ROPE ] ← [
["-gray", "-grey", NIL],
["-red", "-r", "-rd"],
["-grn", "-green", "-g"],
["-blu", "-blue", "-b"],
["-alpha", "-a", "-alp"],
["-depth", "-d", "-z"]
];
FindFile: PROC [fileRoot: Rope.ROPE, names: RECORD[ pref, alt, other: Rope.ROPE ] ]
RETURNS
[name: Rope.ROPE] ~ {
ok: BOOLEANTRUE;
[] ← FS.FileInfo[name ← PasteInLabel[fileRoot, names.pref]
     ! FS.Error => {ok ← FALSE; CONTINUE}];
IF ok THEN RETURN[ name ] ELSE ok ← TRUE;
[] ← FS.FileInfo[name ← PasteInLabel[fileRoot, names.alt]
     ! FS.Error => {ok ← FALSE; CONTINUE}];
IF ok THEN RETURN[ name ] ELSE ok ← TRUE;
[] ← FS.FileInfo[name ← PasteInLabel[fileRoot, names.other]
     ! FS.Error => {ok ← FALSE; CONTINUE}];
IF ok THEN RETURN[ name ] ELSE ok ← TRUE;
[] ← FS.FileInfo[name ← fileRoot ! FS.Error => {ok ← FALSE; CONTINUE}];
IF ok THEN RETURN[ name ]
ELSE {
SIGNAL ThreeDBasics.Error[[
$MisMatch,
Rope.Cat[" Can't find ", fileRoot, " or with extensions: ",
   Rope.Cat[names.pref, " ", names.alt, " ", names.other] ]
]];
RETURN[ NIL ];
};
};
GetAIS: PUBLIC PROC[context: REF Context, fileRoot: Rope.ROPE ← "Temp.ais",
      xOffset, yOffset: INTEGER ← 0, center: BOOLEANTRUE ]
     RETURNS
[ xSize, ySize: INTEGER] ~ {
numFiles: NAT;
names: ARRAY[0..5) OF RECORD[ pref, alt, other: Rope.ROPE ];
inSamples: ARRAY[0..5) OF SampleMap ← ALL[NIL];
pixelsFromAIS: PixelMap;      -- place to put sample maps if needed elsewhere
IF context.stopMe^ THEN RETURN[0, 0]; -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
fileRoot ← SceneUtilities.PrependWorkingDirectory[context, fileRoot];
IF context.pixels = NIL THEN RenderWithPixels.AllocatePixelMemory[ context ]; -- image bfr
SELECT context.class.displayType FROM
$Dithered, $PseudoColor => {
names[0] ← [NIL, NIL, NIL];
numFiles ← 1;
};
$Gray => {
names[0] ← standardNames[0];
numFiles ← 1;
};
$FullColor, $ImagerFullClr  => {
names[0] ← standardNames[1];
names[1] ← standardNames[2];
names[2] ← standardNames[3];
numFiles ← 3;
};
ENDCASE => SIGNAL ThreeDBasics.Error[[$MisMatch, "Bad RenderMode"]];
IF context.antiAliasing
THEN { names[numFiles] ← standardNames[4]; numFiles ← numFiles + 1; };
IF context.depthBuffering
THEN { names[numFiles] ← standardNames[5]; numFiles ← numFiles + 1; };
FOR i: NAT IN [0..numFiles) DO   -- get a file for each pixel entry
fileName: Rope.ROPE ← FindFile[
fileRoot, names[i]
! ThreeDBasics.Error => IF reason.code = $MisMatch THEN RESUME
];
IF fileName # NIL
THEN {
box: Box;
inSamples[i] ← ConvertRasterObject.SampleMapFromAIS[
aisfile: fileName, useVM: TRUE
];
box ← ImagerSample.GetBox[ inSamples[i] ];
xSize ← SF.SizeF[box]; ySize ← SF.SizeS[box];
IF context.stopMe^ THEN RETURN[0, 0]; -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
IF context.viewer # NIL      -- on viewer, put pixels there
THEN {
context.class.updateViewer[context];  -- get viewPort, etc.
context.class.drawInViewer[    -- get pixel map from viewer
context,
NEW[ImagerProcRec ← [
WriteToViewer,
LIST[ NEW[NAT ← i], inSamples[i],
NEW[INTEGER ← xOffset], NEW[INTEGER ← yOffset],
  NEW
[BOOLEAN ← center]
 ]
]]
];
IF labelsEnabled THEN {
intPos: IntegerPair; pos: Pair;
viewerBox: Box ← [
min: [ f: context.viewer.cx, s: context.viewer.cy ],
max: [ f: context.viewer.cw+context.viewer.cx,
  s: context.viewer.ch+context.viewer.cy ]
];
[intPos, , ] ← ComputeBox[viewerBox, box, xOffset, xOffset, center];
pos ← [ 0.02 + 1.0 * intPos.x / context.pixels.box.max.f,
  0.02 + 1.0 * intPos.y / context.pixels.box.max.s ];
context.class.draw2DRope[
context: context, rope: fileName, position: pos, size: 20 * xSize / 640.0
];
};
}
ELSE {
RenderWithPixels.CopyUnder should work without the following commented:
IF context.viewPort # NIL
THEN {
box ← [ [Real.Round[context.viewPort.y], Real.Round[context.viewPort.x]],
  [Real.Round[context.viewPort.h], Real.Round[context.viewPort.w]] ];
box.max.s ← box.max.s + box.min.s; box.max.f ← box.max.f + box.min.f;
}
ELSE box ← context.pixels.box;
TransferSamples[
ImagerSample.Clip[context.pixels[i], box], inSamples[i],
xOffset, yOffset, center
];
};
};
ENDLOOP;
pixelsFromAIS ← ImagerPixel.MakePixelMap[
inSamples[0], inSamples[1], inSamples[2], inSamples[3], inSamples[4]
];
context.props ← Atom.PutPropOnList[context.props, $TempPixels, pixelsFromAIS];
};
GetRGB: PUBLIC PROC[context: REF Context, fileRoot: Rope.ROPE ← "Temp.rgb",
      xOffset, yOffset: INTEGER ← 0, center: BOOLEANTRUE ]
     RETURNS
[ xSize, ySize: INTEGER] ~ {
SampleMapsFromRGB: PROC[ fileName: Rope.ROPE ] ~ {
in: IO.STREAMFS.StreamOpen[fileName: fileName];
box: Box ← [[f: 0, s: 0], [f: 720, s: 486]];
rBuf: ImagerSample.SampleBuffer ← ImagerSample.NewSamples[720];
gBuf: ImagerSample.SampleBuffer ← ImagerSample.NewSamples[720];
bBuf: ImagerSample.SampleBuffer ← ImagerSample.NewSamples[720];
FOR i: NAT IN [0..3) DO
inSamples[i] ← ImagerSample.NewSampleMap[
box: box, bitsPerSample: 8, bitsPerLine: 720*8
];
ENDLOOP;
FOR y: NAT IN [0..486) DO
FOR x: NAT IN [0..720) DO
rBuf[x] ← ORD[IO.GetChar[in]];
gBuf[x] ← ORD[IO.GetChar[in]];
bBuf[x] ← ORD[IO.GetChar[in]];
ENDLOOP;
ImagerSample.PutSamples[map: inSamples[0], initIndex: [y, 0], buffer: rBuf, count: 720];
ImagerSample.PutSamples[map: inSamples[1], initIndex: [y, 0], buffer: gBuf, count: 720];
ImagerSample.PutSamples[map: inSamples[2], initIndex: [y, 0], buffer: bBuf, count: 720];
IF context.stopMe^ THEN RETURN[]; -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
ENDLOOP;
IO.Close[in];
};
box: Box;
inSamples: ARRAY[0..3) OF SampleMap ← ALL[NIL];
pixelsFromAIS: PixelMap;      -- place to put sample maps if needed elsewhere
IF context.stopMe^ THEN RETURN[0, 0]; -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
fileRoot ← SceneUtilities.PrependWorkingDirectory[context, fileRoot];
IF context.pixels = NIL THEN RenderWithPixels.AllocatePixelMemory[ context ]; -- image bfr
IF context.class.displayType # $FullColor
THEN SIGNAL ThreeDBasics.Error[[$MisMatch, "Must be a full-color display"]];
SampleMapsFromRGB[ fileRoot ];
box ← ImagerSample.GetBox[ inSamples[0] ];
xSize ← SF.SizeF[box]; ySize ← SF.SizeS[box];
IF context.viewer # NIL      -- on viewer, put pixels there
THEN {
context.class.updateViewer[context];  -- get viewPort, etc.
FOR i: NAT IN [0..3) DO
context.class.drawInViewer[    -- get pixel map from viewer
context,
NEW[ImagerProcRec ← [
WriteToViewer,
LIST[ NEW[NAT ← i], inSamples[i],
NEW[INTEGER ← xOffset], NEW[INTEGER ← yOffset],
  NEW
[BOOLEAN ← center]
 ]
]]
];
ENDLOOP;
IF labelsEnabled THEN {
intPos: IntegerPair; pos: Pair;
viewerBox: Box ← [
min: [ f: context.viewer.cx, s: context.viewer.cy ],
max: [ f: context.viewer.cw+context.viewer.cx,
  s: context.viewer.ch+context.viewer.cy ]
];
[intPos, , ] ← ComputeBox[viewerBox, box, xOffset, xOffset, center];
pos ← [ 0.02 + 1.0 * intPos.x / context.pixels.box.max.f,
  0.02 + 1.0 * intPos.y / context.pixels.box.max.s ];
context.class.draw2DRope[
context: context, rope: fileRoot, position: pos, size: 20 * xSize / 640.0
];
};
}
ELSE {
IF context.viewPort # NIL
THEN {
box ← [ [Real.Round[context.viewPort.y], Real.Round[context.viewPort.x]],
  [Real.Round[context.viewPort.h], Real.Round[context.viewPort.w]] ];
box.max.s ← box.max.s + box.min.s; box.max.f ← box.max.f + box.min.f;
}
ELSE box ← context.pixels.box;
FOR i: NAT IN [0..3) DO
TransferSamples[
ImagerSample.Clip[context.pixels[i], box], inSamples[i],
xOffset, yOffset, center
];
ENDLOOP;
};
pixelsFromAIS ← ImagerPixel.MakePixelMap[ inSamples[0], inSamples[1], inSamples[2] ];
context.props ← Atom.PutPropOnList[context.props, $TempPixels, pixelsFromAIS];
};
WriteToViewer: ImagerProc ~ {  -- get pixelmap from viewer and get sample map into it
PROC[ context: REF Context, imagerCtx: Imager.Context, data: REF ANYNIL ];
DoWrite: PROC[pixelMap: PixelMap] ~ {
list: LORANARROW[data];
i: NATNARROW[list.first, REF NAT]^;
TransferSamples[
dstMap: pixelMap[i],
srcMap: NARROW[list.rest.first],
xOffset: NARROW[list.rest.rest.first, REF INTEGER]^,
yOffset: NARROW[list.rest.rest.rest.first, REF INTEGER]^,
center: NARROW[list.rest.rest.rest.rest.first, REF BOOLEAN]^
];
};
ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoWrite, context.viewPort^];
};
TransferSamples: PROC[ dstMap, srcMap: SampleMap, xOffset, yOffset: INTEGER,
        center: BOOLEANTRUE ] ~ {
srcBox: Box ← ImagerSample.GetBox[srcMap]; dstBox: Box ← ImagerSample.GetBox[dstMap];
dstMin, srcMin, size: IntegerPair;
[dstMin, srcMin, size] ← ComputeBox[dstBox, srcBox, xOffset, yOffset, center];
ImagerSample.BasicTransfer[
dst: dstMap, src: srcMap,
dstMin: [f: dstMin.x, s: dstMin.y], -- position
srcMin: [f: srcMin.x, s: srcMin.y],
size: [f: size.x, s: size.y]    -- clip
];
[
IF center
THEN {     -- Shift larger sample map to fit in center of smaller, then offset
xDelta ← xOffset + (INTEGER[SF.SizeF[srcBox]] - SF.SizeF[dstBox]) /2;
yDelta ← -yOffset + (INTEGER[SF.SizeS[srcBox]] - SF.SizeS[dstBox]) /2;
}
ELSE {        -- shift tio align with bottom of viewport, then offset
xDelta ← xOffset;
yDelta ← -yOffset + (INTEGER[SF.SizeS[srcBox]] - SF.SizeS[dstBox]);
};
ImagerSample.BasicTransfer[
dst: dstMap, src: srcMap,
dstMin: [f: dstBox.min.f + MAX[0, -xDelta], s: dstBox.min.s + MAX[0, -yDelta]], -- position
srcMin: [f: srcBox.min.f + MAX[0, xDelta], s: srcBox.min.s + MAX[0, yDelta]],
size: [ f: MIN[ SF.SizeF[srcBox] - MAX[0, xDelta], SF.SizeF[dstBox] - MAX[0, -xDelta] ],
  s: MIN[ SF.SizeS[srcBox] - MAX[0, yDelta], SF.SizeS[dstBox] - MAX[0, -yDelta] ]
  ]  -- clip
];
};
ComputeBox: PROC[ box1, box2: Box, xOffset, yOffset: INTEGER, center: BOOLEANTRUE ]
    RETURNS[box1Pos, box2Pos, size: IntegerPair] ~ {
xDelta, yDelta: INTEGER ← 0;
IF center
THEN {     -- Shift larger sample map to fit in center of smaller, then offset
xDelta ← xOffset + (INTEGER[SF.SizeF[box2]] - SF.SizeF[box1]) /2;
yDelta ← -yOffset + (INTEGER[SF.SizeS[box2]] - SF.SizeS[box1]) /2;
}
ELSE {        -- shift tio align with bottom of viewport, then offset
xDelta ← xOffset;
yDelta ← -yOffset + (INTEGER[SF.SizeS[box2]] - SF.SizeS[box1]);
};
box1Pos ← [x: box1.min.f + MAX[0, -xDelta], y: box1.min.s + MAX[0, -yDelta]]; -- position
box2Pos ← [x: box2.min.f + MAX[0, xDelta], y: box2.min.s + MAX[0, yDelta]];
size ← [ x: MIN[ SF.SizeF[box2] - MAX[0, xDelta], SF.SizeF[box1] - MAX[0, -xDelta] ], -- clip
  y: MIN[ SF.SizeS[box2] - MAX[0, yDelta], SF.SizeS[box1] - MAX[0, -yDelta] ] ];
};
WriteFromViewer: ImagerProc ~ {
PROC[ context: REF Context, imagerCtx: Imager.Context, data: REF ANYNIL ];
DoWrite: PROC[pixelMap: PixelMap] ~ {
list: LORANARROW[data];
i: NATNARROW[list.first, REF NAT]^;
fileName: ROPENARROW[list.rest.first];
ConvertRasterObject.AISFromSampleMap[fileName, pixelMap[i]];
};
ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoWrite, context.viewPort^];
};
PutAIS: PUBLIC PROC[context: REF Context, fileRoot: Rope.ROPE ← "Temp.ais",
       doEverything: BOOLEANFALSE ] ~ {
addOn: NATIF context.antiAliasing THEN 1 ELSE 0;
numFiles: NAT;
names: ARRAY[0..4) OF Rope.ROPEALL[NIL];
IF context.depthBuffering THEN addOn ← addOn + 1;
fileRoot ← SceneUtilities.PrependWorkingDirectory[context, fileRoot];
SELECT context.class.displayType FROM
$Dithered, $PseudoColor => { names[0] ← NIL; numFiles ← 1; };
$Gray => { names[0] ← "-gray"; numFiles ← 1; };
$FullColor, $ImagerFullClr  => {
names[0] ← "-red"; names[1] ← "-grn"; names[2] ← "-blu"; numFiles ← 3;
};
ENDCASE => SIGNAL ThreeDBasics.Error[[$MisMatch, "Bad RenderMode"]];
IF context.antiAliasing AND doEverything
THEN { names[numFiles] ← "-alpha"; numFiles ← numFiles + 1; };
IF context.depthBuffering AND doEverything
THEN { names[numFiles] ← "-depth"; numFiles ← numFiles + 1; };
SurfaceRender.ValidateContext[context];
FOR i: NAT IN [0..numFiles) DO   -- write a file for each pixel entry
IF context.viewer # NIL
THEN {
context.class.updateViewer[context];  -- get viewPort, etc.
context.class.drawInViewer[
context,
NEW[ImagerProcRec ← [
WriteFromViewer,
LIST[ NEW[NAT ← i], PasteInLabel[fileRoot, names[i]] ]
]]
]
}
ELSE {
box: Box;
IF context.viewPort # NIL
THEN {
box ← [ [Real.Round[context.viewPort.y], Real.Round[context.viewPort.x]],
  [Real.Round[context.viewPort.h], Real.Round[context.viewPort.w]] ];
box.max.s ← box.max.s + box.min.s; box.max.f ← box.max.f + box.min.f;
}
ELSE box ← context.pixels.box;
IF context.stopMe^ THEN RETURN[]; -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
ConvertRasterObject.AISFromSampleMap[
PasteInLabel[fileRoot, names[i]],
ImagerSample.Clip[context.pixels[i], box]
];
};
ENDLOOP;
};
WriteRGBFromViewer: ImagerProc ~ {
PROC[ context: REF Context, imagerCtx: Imager.Context, data: REF ANYNIL ];
DoWrite: PROC[pixelMap: PixelMap] ~ {
list: LORANARROW[data];
fileName: ROPENARROW[list.first];
RGBFromSampleMaps[context, fileName, pixelMap[0], pixelMap[1], pixelMap[2] ];
};
ImagerBackdoor.AccessBufferRectangle[imagerCtx, DoWrite, context.viewPort^];
};
PutRGB: PUBLIC PROC[context: REF Context, fileRoot: Rope.ROPE ← "Temp.rgb" ] ~ {
fileRoot ← SceneUtilities.PrependWorkingDirectory[context, fileRoot];
IF context.class.displayType # $FullColor
THEN SIGNAL ThreeDBasics.Error[[$MisMatch, "Must be a full-color image"]];
SurfaceRender.ValidateContext[context];
IF context.pixels # NIL
THEN RGBFromSampleMaps[ context, fileRoot,
         context.pixels[0], context.pixels[1], context.pixels[2] ]
ELSE IF context.viewer # NIL THEN {
context.class.updateViewer[context];  -- get viewPort, etc.
context.class.drawInViewer[
context,
NEW[ImagerProcRec ← [ WriteRGBFromViewer, LIST[ fileRoot ] ]]
]
}
};
RGBFromSampleMaps: PROC[ context: REF Context, fileName: Rope.ROPE,
         rMap, gMap, bMap: SampleMap ] ~ {
out: IO.STREAMFS.StreamOpen[fileName: fileName, accessOptions: create];
box: Box ← ImagerSample.GetBox[rMap];
xSize: NAT ← box.max.f - box.min.f;
ySize: NAT ← box.max.s - box.min.s;
yMin: INTEGER ← (486 - ySize) / 2;
xMin: INTEGER ← (720 - xSize) / 2;
rBuf: ImagerSample.SampleBuffer ← ImagerSample.NewSamples[720];
gBuf: ImagerSample.SampleBuffer ← ImagerSample.NewSamples[720];
bBuf: ImagerSample.SampleBuffer ← ImagerSample.NewSamples[720];
FOR y: NAT IN [0..485] DO
IF y >= yMin AND y < yMin + ySize THEN { -- get a line from sample map
mpx: NAT ← box.min.f;
mpy: NAT ← y - yMin + box.min.s;
ImagerSample.GetSamples[map: rMap, initIndex: [mpy, mpx], buffer: rBuf, count: xSize];
ImagerSample.GetSamples[map: gMap, initIndex: [mpy, mpx], buffer: gBuf, count: xSize];
ImagerSample.GetSamples[map: bMap, initIndex: [mpy, mpx], buffer: bBuf, count: xSize];
};
FOR x: NAT IN [0..719] DO
r,g,b: BYTE;
IF y < yMin OR y > yMin + ySize OR x < xMin OR x > xMin + xSize
THEN r ← g ← b ← 0
ELSE { r ← rBuf[x - xMin]; g ← gBuf[x - xMin]; b ← bBuf[x - xMin]; };
IO.PutChar[out, VAL[r]]; IO.PutChar[out, VAL[g]]; IO.PutChar[out, VAL[b]];
ENDLOOP;
IF context.stopMe^ THEN RETURN[]; -- shut down if stop signal received
CedarProcess.CheckAbort[];   -- respond to Stop button
ENDLOOP;
IO.Close[out];
};
END.