-- CGAISImageImpl.mesa
-- Last changed by Doug Wyatt, October 4, 1982 11:35 am
-- Last changed by Paul Rovner, August 10, 1983 11:34 am

DIRECTORY
Basics USING [LongDiv, LongMult],
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],
GraphicsOps USING [], -- exports only
GraphicsBasic USING [black],
FS USING [Open, OpenFile, Read],
PrincOps USING [wordsPerPage],
Rope USING [ROPE],
VM USING [Interval, Free, Allocate, AddressForPageNumber];

CGAISImageImpl: CEDAR MONITOR
IMPORTS Basics, CGArea, CGContext, CGMatrix, CGStorage, FS, VM
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.

Data: TYPE = REF DataRep;
DataRep: TYPE = RECORD [
file: FS.OpenFile, -- the AIS file
rasterBase: INT, -- where the raster begins
rasterPages: INT, -- 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: FS.OpenFile ← [NIL];
cachedSpace: VM.Interval ← [0, 0];

-- end monitored data


NewAisImage: PUBLIC PROC[name: Rope.ROPE] RETURNS[Data] = {
RETURN[NewAisImageFromCapability[FS.Open[name]]];
};

NewAisImageFromCapability: PUBLIC PROC[file: FS.OpenFile] 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: VM.Interval ← VM.Allocate[count: 4]; -- for reading header
{ ENABLE UNWIND => VM.Free[space];
header ← lp ← VM.AddressForPageNumber[space.page];
FS.Read[file: file, from: 0, nPages: 4, to: lp];
IF header.password=AIS.password THEN
part ← LOOPHOLE[header+SIZE[AIS.Header]]
ELSE ERROR MalformedFile; -- Wrong password
apages ← header.attributeLength/PrincOps.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 ← Basics.LongMult[lines, wpl]; -- number of words in raster part
rpages ← Basics.LongDiv[rwords+(PrincOps.wordsPerPage-1), PrincOps.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]];
};
VM.Free[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 ← [NIL]; -- and release lock
file: FS.OpenFile ← data.file;
base: INT ← data.rasterBase;
pages: INT ← data.rasterPages;
space: VM.Interval ← cachedSpace;
area: CGArea.Ref;
IF cachedFile#file THEN { create: BOOLEANFALSE;
-- first ensure a large enough space
IF space=[0, 0] THEN create ← TRUE
ELSE IF space.count<pages THEN {
VM.Free[space]; cachedSpace ← [0, 0]; create ← TRUE;
};
IF create THEN space ← VM.Allocate[count: pages];
-- now map the space onto the new file
FS.Read[
file: file, from: base, nPages: space.count, to: VM.AddressForPageNumber[space.page]];
cachedFile ← file; cachedSpace ← space;
};
src.xbase ← VM.AddressForPageNumber[space.page];
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;
};

}.