ImagerAISPixelArrayImpl.mesa
Copyright © 1984, 1985 by Xerox Corporation. All rights reserved.
Doug Wyatt, May 28, 1985 6:12:42 pm PDT
Michael Plass, August 19, 1985 4:10:39 pm PDT
DIRECTORY
AISFileFormat USING [AttributeHeader, bytesPerWord, CommentPart, nil, PartHeader, passwordValue, PhotometryPart, PlacementPart, RasterPart, sizeAttributeHeader, sizeCommentPart, sizePartHeader, sizePhotometryPart, sizePlacementPart, sizeRasterPart, sizeUCACoding, UCACoding, wordsPerPage],
Basics USING [BITSHIFT, bitsPerWord, BoundsCheck, bytesPerWord],
CountedVM USING [Handle, SimpleAllocate],
FS USING [StreamOpen],
ImagerPixelArray,
ImagerPixelArrayDefs,
ImagerPixelArrayPrivate,
ImagerSample,
ImagerTransformation USING [Create, Transformation],
IO USING [EndOfStream, GetIndex, SetIndex, STREAM, UnsafeGetBlock],
Rope USING [FromProc, ROPE];
ImagerAISPixelArrayImpl: CEDAR MONITOR LOCKS data USING data: Data
IMPORTS Basics, CountedVM, FS, ImagerPixelArray, ImagerSample, ImagerTransformation, IO, Rope
EXPORTS ImagerPixelArray, ImagerPixelArrayDefs
~ BEGIN OPEN ImagerPixelArray, ImagerPixelArrayPrivate, ImagerPixelArrayDefs;
ROPE: TYPE ~ Rope.ROPE;
STREAM: TYPE ~ IO.STREAM;
PixelArrayClass: TYPE ~ ImagerPixelArrayPrivate.PixelArrayClass;
PixelArrayClassRep: PUBLIC TYPE ~ ImagerPixelArrayPrivate.PixelArrayClassRep;
Data: TYPE ~ REF DataRep;
DataRep:
TYPE ~
MONITORED
RECORD[
stream: STREAM, -- a stream on the AIS file
comments: LIST OF ROPE ← NIL, -- comments from header
scanCount: CARDINAL ← 0, -- number of scan lines
scanLength: CARDINAL ← 0, -- pixels per scan line
samplesPerPixel: CARDINAL ← 0, -- samples per pixel
bitsPerSample: CARDINAL ← 0, -- bits per sample
wordsPerScanLine: CARDINAL ← 0, -- 16-bit words per scan line
scanLinesPerBlock: CARDINAL ← 0, -- number of scan lines per block
headerLength: INT ← 0, -- number of words in the header part
wordsPerBlock: INT ← 0, -- number of words per block
paddingPerBlock: INT ← 0, -- words of padding at the end of a block (in the file)
smin, smax: CARDINAL ← 0, -- buffer currently contains scan lines [smin..smax)
buffer: CountedVM.Handle ← NIL
];
wordSizesMatch: BOOL ~ AISFileFormat.bytesPerWord=Basics.bytesPerWord;
assertWordSizesMatch:
BOOL[
TRUE..
TRUE] ~ wordSizesMatch;
AIS file format defines a "word" to be 16 bits. Parts of the following code assume that a WORD is the same size: places to note include buffer allocation and "wordsPerLine" supplied to the ImagerSample unpacking primitives.
defaultScanLinesPerBlock: NAT ← 8;
ProduceError:
PROC [explanation:
ROPE] ~ {
ERROR Error[[code: $invalidAISFile, explanation: explanation]];
};
Assert:
PROC [assertion:
BOOL] ~ {
IF NOT assertion THEN ProduceError["AIS file structure is inconsistent."];
};
GetWordIndex:
PROC [stream:
STREAM]
RETURNS[wordIndex:
INT] ~ {
Note: "words" means the 16-bit words used by AISFileFormat!
byteIndex: INT ~ IO.GetIndex[stream];
wordIndex ← byteIndex/AISFileFormat.bytesPerWord;
};
SetWordIndex:
PROC [stream:
STREAM, wordIndex:
INT] ~ {
Note: "words" means the 16-bit words used by AISFileFormat!
byteIndex: INT ~ wordIndex*AISFileFormat.bytesPerWord;
IO.SetIndex[stream, byteIndex];
};
SkipWords:
PROC [stream:
STREAM, wordCount:
INT] ~ {
Note: "words" means the 16-bit words used by AISFileFormat!
byteCount: INT ~ wordCount*AISFileFormat.bytesPerWord;
IO.SetIndex[stream, IO.GetIndex[stream]+byteCount];
};
ReadWords:
UNSAFE
PROC [stream:
STREAM, base:
LONG
POINTER, words:
INT] ~
UNCHECKED {
Note: "words" means the 16-bit words used by AISFileFormat!
bytes: INT ~ words*AISFileFormat.bytesPerWord;
IF IO.UnsafeGetBlock[stream, [base: LOOPHOLE[base], count: bytes]]=bytes THEN NULL
ELSE ERROR IO.EndOfStream[stream]; -- file too short
};
LoadBlock:
INTERNAL PROC [data: Data, s:
CARDINAL] ~ {
linesPerBlock: CARDINAL ~ data.scanLinesPerBlock;
block: NAT ~ s/linesPerBlock; -- block number containing s
smin: NAT ~ block*linesPerBlock; -- first scan line in block
smax: NAT ~ MIN[smin+linesPerBlock, data.scanCount];
wordIndex: INT ~ data.headerLength+block*(data.wordsPerBlock+data.paddingPerBlock);
wordCount: INT ~ INT[smax-smin]*INT[data.wordsPerScanLine];
IO.SetIndex[self: data.stream, index: wordIndex*AISFileFormat.bytesPerWord];
IF wordCount>data.buffer.words THEN ERROR
ELSE TRUSTED { ReadWords[stream: data.stream, base: data.buffer.pointer, words: wordCount] };
data.smin ← smin; data.smax ← smax;
};
aisClass: PixelArrayClass ~
NEW[PixelArrayClassRep ← [
type: $AIS,
MaxSampleValue: AISMaxSampleValue,
UnsafeGetSamples: AISUnsafeGetSamples
]];
AISMaxSampleValue:
PROC [pa: PixelArray, i:
NAT]
RETURNS [Sample] ~ {
data: Data ~ NARROW[pa.data];
[] ← Basics.BoundsCheck[i, pa.samplesPerPixel];
RETURN[Basics.BITSHIFT[WORD.LAST, INTEGER[data.bitsPerSample]-Basics.bitsPerWord]];
};
AISUnsafeGetSamples:
UNSAFE
PROC [pa: PixelArray, i:
NAT, s, f:
INT,
samples: UnsafeSamples, count:
NAT] ~ {
data: Data ~ NARROW[pa.data];
i0: NAT ~ Basics.BoundsCheck[i, pa.samplesPerPixel];
s0: NAT ~ s; sRem: NAT ~ pa.sSize-s; -- bounds check: s IN[0..sSize]
f0: NAT ~ f; fRem: NAT ~ pa.fSize-f; -- bounds check: f IN[0..fSize]
[] ← Basics.BoundsCheck[0, sRem];
IF count=0 THEN RETURN ELSE [] ← Basics.BoundsCheck[count-1, fRem];
IF data.samplesPerPixel#1 THEN ERROR Error[[$notImplemented,
"AIS files with samplesPerPixel>1 are not yet supported."]];
TRUSTED { EntryGetSamples[data: data, s0: s0, f0: f0, samples: samples, count: count] };
};
EntryGetSamples:
ENTRY
UNSAFE
PROC [data: Data, s0:
NAT, f0:
NAT, samples: UnsafeSamples, count:
NAT] ~ {
ENABLE UNWIND => NULL;
IF s0 NOT IN[data.smin..data.smax) THEN LoadBlock[data, s0];
TRUSTED { ImagerSample.UnsafeGetF[samples: samples, count: count,
base: data.buffer.pointer, wordsPerLine: data.wordsPerScanLine,
bitsPerSample: data.bitsPerSample, s: s0-data.smin, f: f0] };
};
FromAIS:
PUBLIC
PROC [name:
ROPE]
RETURNS [PixelArray] ~ {
OPEN AISFileFormat;
stream: STREAM ~ FS.StreamOpen[name];
data: Data ~ NEW[DataRep ← [stream: stream]];
header: AttributeHeader;
raster: RasterPart;
uca: UCACoding;
placement: PlacementPart;
photometry: PhotometryPart;
rasterFound, placementFound, photometryFound: BOOL ← FALSE;
m: ImagerTransformation.Transformation ← NIL; -- from [s, f] to [x, y]
class: PixelArrayClass ← NIL;
TRUSTED { ReadWords[stream, @header, sizeAttributeHeader] };
IF header.password#passwordValue THEN ProduceError["AIS password value is wrong."];
Assert[header.length>0 AND (header.length MOD wordsPerPage)=0];
data.headerLength ← header.length;
FOR firstPart:
BOOL ←
TRUE,
FALSE
WHILE GetWordIndex[stream]<data.headerLength
DO
part: PartHeader;
TRUSTED { ReadWords[stream, @part, sizePartHeader] };
SELECT part.type
FROM
nil => { Assert[part.length=0]; EXIT }; -- should be a zero word
raster => {
Assert[firstPart]; -- raster part must be first
Assert[part.length>=(sizePartHeader+sizeRasterPart)];
TRUSTED { ReadWords[stream, @raster, sizeRasterPart] };
Assert[raster.scanCount>0 AND raster.scanLength>0 AND raster.samplesPerPixel>0];
SELECT raster.codingType
FROM
uca => {
sizeCoding: NAT ~ part.length-(sizePartHeader+sizeRasterPart);
Assert[sizeCoding IN[sizeUCACoding-1..sizeUCACoding]];
TRUSTED { ReadWords[stream, @uca, sizeCoding] };
IF uca.bitsPerSample=0 THEN uca.bitsPerSample ← 1; -- kludge
Assert[INT[uca.bitsPerSample]*INT[raster.samplesPerPixel]*INT[raster.scanLength]
<=INT[16--bitsPerWord--]*INT[uca.wordsPerScanLine]];
IF sizeCoding<sizeUCACoding THEN Assert[uca.scanLinesPerBlock=nil];
};
ENDCASE => ProduceError["Unknown AIS coding type."];
rasterFound ← TRUE;
};
placement => {
IF placementFound THEN ProduceError["AIS file has more than one placement part."];
Assert[part.length=(sizePartHeader+sizePlacementPart)];
TRUSTED { ReadWords[stream, @placement, sizePlacementPart] };
IF placement.xLeft=-1 AND placement.yBottom=-1
AND placement.xWidth=-1 AND placement.yHeight=-1 THEN NULL
ELSE Assert[placement.xWidth>0 AND placement.yHeight>0];
placementFound ← TRUE;
};
photometry => {
IF photometryFound THEN ProduceError["AIS file has more than one photometry part."];
Assert[part.length>=(sizePartHeader+sizePhotometryPart)];
TRUSTED { ReadWords[stream, @photometry, sizePhotometryPart] };
-- Ignore histogram, if any
SkipWords[stream, part.length-(sizePartHeader+sizePhotometryPart)];
photometryFound ← TRUE;
};
comment => {
comment: CommentPart;
length: NAT ← 0; -- number of characters in the string
rope: ROPE ← NIL;
Assert[part.length>sizePartHeader AND (part.length-sizePartHeader)<=sizeCommentPart];
TRUSTED { ReadWords[stream, @comment, part.length-sizePartHeader] };
length ← ORD[comment[0]];
Assert[(part.length-sizePartHeader)=(1+length+1)/2];
{
-- turn the comment into a ROPE
i: NAT ← 0;
p: PROC RETURNS [CHAR] ~ { RETURN[comment[i ← i+1]] };
rope ← Rope.FromProc[len: length, p: p];
};
data.comments ← CONS[rope, data.comments];
};
ENDCASE => SkipWords[stream, part.length-sizePartHeader]; -- unknown part type
ENDLOOP;
IF NOT rasterFound THEN ProduceError["AIS file has no raster part."];
data.scanCount ← raster.scanCount;
data.scanLength ← raster.scanLength;
data.samplesPerPixel ← raster.samplesPerPixel;
data.bitsPerSample ← uca.bitsPerSample;
data.wordsPerScanLine ← uca.wordsPerScanLine;
IF uca.scanLinesPerBlock=nil
THEN {
data.scanLinesPerBlock ← MIN[defaultScanLinesPerBlock, data.scanCount];
data.paddingPerBlock ← 0;
}
ELSE {
data.scanLinesPerBlock ← uca.scanLinesPerBlock;
data.paddingPerBlock ← uca.paddingPerBlock;
};
data.wordsPerBlock ← INT[data.wordsPerScanLine]*INT[data.scanLinesPerBlock];
data.buffer ← CountedVM.SimpleAllocate[data.wordsPerBlock];
SELECT raster.scanDirection
FROM
0, 8 => m ← ImagerTransformation.Create[1, 0, 0, 0, 1, 0];
3 => m ← ImagerTransformation.Create[0, 1, 0, -1, 0, raster.scanCount];
ENDCASE => ERROR Error[[$unimplementedAISFile,
"AIS file has unimplemented scanDirection"]];
RETURN[NEW[PixelArrayRep ← [samplesPerPixel: data.samplesPerPixel,
sSize: data.scanCount, fSize: data.scanLength, m: m, class: aisClass, data: data]]];
};
Join3AIS:
PUBLIC
PROC [name1, name2, name3:
ROPE]
RETURNS [PixelArray] ~ {
RETURN[ImagerPixelArray.Join3[FromAIS[name1], FromAIS[name2], FromAIS[name3]]];
};
END.