-- CGAISImageImpl.mesa
-- Last changed by Doug Wyatt, October 4, 1982 11:35 am

DIRECTORY
CGAIS USING [APH, Header, password, RasterPart, UCA, UCACodingType],
CGArea USING [Empty, Ref],
CGContext USING [GenBox, Rep],
CGDevice USING [Ref],
CGMatrix USING [Assign, Concat, ConcatTransformation, Make, New, Ref, SetTrans],
CGPrivate USING [Context],
CGSource USING [Ref, Rep],
CGStorage USING [qZone],
ConvertUnsafe USING [AppendRope],
GraphicsOps USING [], -- exports only
GraphicsBasic USING [black],
Directory USING [Lookup],
File USING [Capability, nullCapability, PageCount, PageNumber],
Inline USING [LongDiv, LongMult],
Rope USING [ROPE],
Space USING [Create, CreateUniformSwapUnits, Delete, GetAttributes, Handle, LongPointer,
Map, nullHandle, Unmap, virtualMemory, wordsPerPage];

CGAISImageImpl: CEDAR MONITOR
IMPORTS CGArea, CGContext, CGMatrix, CGStorage, ConvertUnsafe, Directory, Inline, Space
EXPORTS CGPrivate, GraphicsOps, GraphicsBasic = {
OPEN AIS: CGAIS;

Context: TYPE = CGPrivate.Context;
ContextData: TYPE = REF ContextDataRep;
ContextDataRep: PUBLIC TYPE = CGContext.Rep;

ImageObject: PUBLIC TYPE = DataRep; -- export to GraphicsBasic
-- This is cheating!
-- A new representation-independent Image interface is neeeded.
-- But for now, this module exports NewAisImage and ImageBox
-- directly into GraphicsOps.

wordsPerPage: CARDINAL = Space.wordsPerPage;
swapUnitSize: CARDINAL ← 10;

Data: TYPE = REF DataRep;
DataRep: TYPE = RECORD [
file: File.Capability, -- the AIS file
rasterBase: File.PageNumber, -- where the raster begins
rasterPages: File.PageCount, -- number of pages in the raster
scanDirection: CARDINAL, -- scan direction code
scanCount: CARDINAL, -- number of scan lines in raster
scanLength: CARDINAL, -- pixels per scan line
wordsPerLine: CARDINAL, -- words per scan line
miv: CGMatrix.Ref, -- image-to-virtual matrix
matrix: CGMatrix.Ref, -- image-to-device matrix
src: CGSource.Ref -- source info for image
];

dataZone: ZONE = CGStorage.qZone;
srcZone: ZONE = CGStorage.qZone;
repZone: ZONE = CGStorage.qZone;

MalformedFile: ERROR = CODE;
NotImplemented: ERROR = CODE;

-- Monitored data: cache most recently mapped space

cachedFile: File.Capability ← File.nullCapability;
cachedSpace: Space.Handle ← Space.nullHandle;

-- end monitored data


NewAisImage: PUBLIC PROC[name: Rope.ROPE] RETURNS[Data] = {
file: File.Capability;
TRUSTED { string: STRING ← [200];
ConvertUnsafe.AppendRope[to: string, from: name];
file ← Directory.Lookup[fileName: string] };
-- may raise Directory.Error
RETURN[NewAisImageFromCapability[file]];
};

NewAisImageFromCapability: PUBLIC PROC[file: File.Capability] RETURNS[Data] = TRUSTED {
lp: LONG POINTER;
header: LONG POINTER TO AIS.Header ← NIL;
part: LONG POINTER TO AIS.APHNIL;
rp: LONG POINTER TO AIS.RasterPart ← NIL;
up: LONG POINTER TO AIS.UCANIL;
lines,pixels,wpl,bps: CARDINAL;
rbase, apages, rpages: CARDINAL;
rwords: LONG CARDINAL;
self: Data ← NIL;
space: Space.Handle ← Space.Create[size: 4, parent: Space.virtualMemory]; -- for reading header
{ ENABLE UNWIND => Space.Delete[space];
Space.Map[space: space, window: [file: file, base: 1]];

header ← lp ← Space.LongPointer[space];
IF header.password=AIS.password THEN
part ← LOOPHOLE[header+SIZE[AIS.Header]]
ELSE ERROR MalformedFile; -- Wrong password
apages ← header.attributeLength/wordsPerPage;
rbase ← 1+apages;
IF part.type=raster THEN {
rp ← LOOPHOLE[part]; part ← part+part.length }
ELSE ERROR MalformedFile; -- Raster part missing
IF rp.samplesPerPixel=1 THEN NULL
ELSE ERROR NotImplemented; -- Can't handle multiple samples per pixel
IF rp.codingType=AIS.UCACodingType THEN
up ← LOOPHOLE[rp+SIZE[AIS.RasterPart]]
ELSE ERROR NotImplemented; -- Coding type not UCA
IF up.scanLinesPerBlock=LAST[CARDINAL] THEN NULL
ELSE ERROR NotImplemented; -- Can't handle blocked encoding yet

lines ← rp.scanCount;
pixels ← rp.scanLength;
wpl ← up.wordsPerScanLine;
bps ← up.bitsPerSample;
rwords ← Inline.LongMult[lines, wpl]; -- number of words in raster part
rpages ← Inline.LongDiv[rwords+(wordsPerPage-1), wordsPerPage]; -- number of pages needed

self ← dataZone.NEW[DataRep ← [file: file, rasterBase: rbase, rasterPages: rpages,
scanDirection: rp.scanDirection, scanCount: lines, scanLength: pixels, wordsPerLine: wpl,
miv: NIL, matrix: NIL, src: NIL]];
SELECT self.scanDirection FROM
2 => self.miv ← CGMatrix.Make[[1,0,0,1,0,0]];
3 => self.miv ← CGMatrix.Make[[1,0,0,-1,0,lines]];
8 => self.miv ← CGMatrix.Make[[0,1,1,0,0,0]];
ENDCASE => ERROR; -- unexpected scanDirection
self.matrix ← CGMatrix.New[]; -- placeholder for image-to-device mapping
self.src ← srcZone.NEW[CGSource.Rep ← [type: array, mode: opaque,
fat: FALSE, bps: bps, color: GraphicsBasic.black, xbase: NIL, xrast: wpl, Get: NIL]];
};
Space.Delete[space];
RETURN[self];
};

DrawImage: PUBLIC PROC[self: Context, image: Data, raw: BOOLEAN] = {
ctx: ContextData ← NARROW[self.data];
data: Data ← image;
T: CGMatrix.Ref ← ctx.matrix; -- virtual to destination mapping
M: CGMatrix.Ref ← data.matrix; -- image to destination mapping
src: CGSource.Ref ← data.src;
wpl: CARDINAL ← data.wordsPerLine;
device: CGDevice.Ref ← ctx.device;

DrawImageEntry: ENTRY PROC = TRUSTED {
ENABLE UNWIND => cachedFile ← File.nullCapability; -- and release lock
file: File.Capability ← data.file;
base: File.PageNumber ← data.rasterBase;
pages: File.PageCount ← data.rasterPages;
space: Space.Handle ← cachedSpace;
area: CGArea.Ref;
IF cachedFile#file THEN { create: BOOLEANFALSE;
-- first ensure a large enough space
IF space=Space.nullHandle THEN create ← TRUE
ELSE IF Space.GetAttributes[space].size>=pages THEN Space.Unmap[space]
ELSE { Space.Delete[space]; cachedSpace ← Space.nullHandle; create ← TRUE };
IF create THEN {
space ← Space.Create[size: pages, parent: Space.virtualMemory];
Space.CreateUniformSwapUnits[size: swapUnitSize, parent: space];
};
-- now map the space onto the new file
Space.Map[space, [file, base]];
cachedFile ← file; cachedSpace ← space;
};
src.xbase ← Space.LongPointer[space];
area ← CGContext.GenBox[ctx,
[xmin: 1, ymin: 1, xmax: data.scanLength-1, ymax: data.scanCount-1], M];
IF NOT CGArea.Empty[area] THEN device.Show[device, area, src,M ];
};

CGMatrix.Assign[M,T];
CGMatrix.SetTrans[M,ctx.cp]; -- translate to current position
IF NOT ctx.yUp THEN CGMatrix.Concat[M,1,0,0,-1];
CGMatrix.ConcatTransformation[M, data.miv.m]; -- M ← miv * T
src.color ← ctx.src.color; src.mode ← ctx.src.mode; src.raw ← raw;
DrawImageEntry[];
};

ImageBox: PUBLIC PROC[image: Data] RETURNS[xmin,ymin,xmax,ymax: REAL] = {
w: REAL ← image.scanLength;
h: REAL ← image.scanCount;
SELECT image.scanDirection FROM
2,3 => RETURN[0,0,w,h];
8 => RETURN[0,0,h,w];
ENDCASE => ERROR; -- Unexpected scanDirection;
};

}.