<> <> <> DIRECTORY AISFileFormat USING [AttributeHeader, bytesPerWord, CommentPart, nil, PartHeader, passwordValue, PhotometryPart, PlacementPart, RasterPart, sizeAttributeHeader, sizeCommentPart, sizePartHeader, sizePhotometryPart, sizePlacementPart, sizeRasterPart, sizeUCACoding, UCACoding, wordsPerPage], FS USING [StreamOpen], ImagerPixelArray, ImagerTransformation USING [Create, Transformation], IO USING [EndOfStream, GetBlock, GetIndex, SetIndex, STREAM, UnsafeGetBlock], Rope USING [FromProc, ROPE], RuntimeError USING [BoundsFault]; ImagerAISPixelArrayImpl: CEDAR PROGRAM IMPORTS FS, ImagerPixelArray, ImagerTransformation, IO, Rope, RuntimeError EXPORTS ImagerPixelArray ~ BEGIN OPEN ImagerPixelArray; ROPE: TYPE ~ Rope.ROPE; STREAM: TYPE ~ IO.STREAM; Data: TYPE ~ REF DataRep; DataRep: TYPE ~ RECORD[ stream: STREAM, -- a stream on the AIS file sSize, fSize: NAT _ 0, -- size in slow and fast directions samplesPerPixel: NAT _ 0, comments: LIST OF ROPE _ NIL, m: ImagerTransformation.Transformation _ NIL, -- from [s, f] to [x, y] lgBitsPerSample: NAT _ 0, rasterIndex: INT _ 0, -- byte index of the beginning of the raster section bytesPerLine: INT _ 0, -- bytes per scan line linesPerBlock: NAT _ 0, -- number of scan lines per block, 0 if no blocks bytesPadding: INT _ 0, -- bytes of padding at the end of each block sBuffer: NAT _ 0, -- scan line currently in the buffer buffer: REF TEXT _ NIL ]; Error: PUBLIC ERROR ~ CODE; Assert: PROC[assertion: BOOL] ~ { IF assertion THEN NULL ELSE ERROR Error }; ReadWords: UNSAFE PROC[stream: STREAM, base: LONG POINTER, words: INT] ~ UNCHECKED { <> bytes: INT ~ words*AISFileFormat.bytesPerWord; IF IO.UnsafeGetBlock[stream, [base: base, count: bytes]]=bytes THEN NULL ELSE ERROR IO.EndOfStream[stream]; -- file too short }; SkipWords: PROC[stream: STREAM, words: INT] ~ { <> bytes: INT ~ words*AISFileFormat.bytesPerWord; IO.SetIndex[stream, IO.GetIndex[stream]+bytes]; }; Open: PROC[name: ROPE] RETURNS[Data] ~ { OPEN AISFileFormat; stream: STREAM ~ FS.StreamOpen[name]; data: Data ~ NEW[DataRep _ [stream: stream]]; header: AttributeHeader; raster: RasterPart; placement: PlacementPart; photometry: PhotometryPart; rasterFound, placementFound, photometryFound: BOOL _ FALSE; TRUSTED { ReadWords[stream, @header, sizeAttributeHeader] }; Assert[header.password=passwordValue]; -- check password Assert[header.length>0 AND (header.length MOD wordsPerPage)=0]; data.rasterIndex _ header.length*bytesPerWord; FOR firstPart: BOOL _ TRUE, FALSE WHILE IO.GetIndex[stream] { 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 => { uca: UCACoding; sizeCoding: NAT ~ part.length-(sizePartHeader+sizeRasterPart); Assert[sizeCoding IN[sizeUCACoding-1..sizeUCACoding]]; TRUSTED { ReadWords[stream, @uca, sizeCoding] }; Assert[INT[uca.bitsPerSample]*INT[raster.samplesPerPixel]*INT[raster.scanLength] <=INT[16--bitsPerWord--]*INT[uca.wordsPerScanLine]]; data.lgBitsPerSample _ SELECT uca.bitsPerSample FROM 1 => 0, 2 => 1, 4 => 2, 8 => 3, 16 => 4, ENDCASE => ERROR Error; -- unsupported bitsPerSample data.bytesPerLine _ INT[uca.wordsPerScanLine]*bytesPerWord; IF sizeCoding ERROR Error; -- unknown coding type rasterFound _ TRUE; }; placement => { Assert[NOT placementFound]; -- at most 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 => { Assert[NOT photometryFound]; -- at most 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; Assert[rasterFound]; -- raster part is mandatory data.fSize _ raster.scanLength; data.sSize _ raster.scanCount; data.samplesPerPixel _ raster.samplesPerPixel; SELECT raster.scanDirection FROM 0, 8 => data.m _ ImagerTransformation.Create[1, 0, 0, 0, 1, 0]; 3 => data.m _ ImagerTransformation.Create[0, 1, 0, -1, 0, raster.scanCount]; ENDCASE => ERROR Error; -- unsupported scanDirection data.buffer _ NEW[TEXT[data.bytesPerLine]]; LoadBuffer[data, 0]; RETURN[data]; }; LoadBuffer: PROC[data: Data, s: NAT] ~ { stream: STREAM ~ data.stream; count: NAT ~ data.bytesPerLine; index: INT _ data.rasterIndex+data.bytesPerLine*s; IF data.linesPerBlock#0 THEN index _ index+data.bytesPadding*(s/data.linesPerBlock); IO.SetIndex[stream, index]; IF IO.GetBlock[self: stream, block: data.buffer, count: count]=count THEN NULL ELSE ERROR IO.EndOfStream[stream]; data.sBuffer _ s; }; FromAIS: PUBLIC PROC[name: ROPE] RETURNS[PixelArray] ~ { data: Data ~ Open[name]; RETURN[NEW[PixelArrayRep _ [class: aisClass, data: data, sSize: data.sSize, fSize: data.fSize, samplesPerPixel: data.samplesPerPixel, m: data.m]]]; }; FromAISSeparations: PUBLIC PROC[name0, name1, name2, name3: ROPE _ NIL] RETURNS[PixelArray] ~ { head, tail: LIST OF PixelArray _ NIL; Append: PROC[name: ROPE] ~ { pa: PixelArray ~ FromAIS[name]; list: LIST OF PixelArray ~ LIST[pa]; IF tail=NIL THEN head _ list ELSE tail.rest _ list; tail _ list; }; IF name0#NIL THEN Append[name0]; IF name1#NIL THEN Append[name1]; IF name2#NIL THEN Append[name2]; IF name3#NIL THEN Append[name3]; RETURN[ImagerPixelArray.JoinPixelArrays[head]]; }; aisClass: ImagerPixelArray.Class ~ NEW[ImagerPixelArray.ClassRep _ [ type: $AIS, MaxSampleValue: AISMaxSampleValue, GetSample: AISGetSample, GetRow: AISGetRow ]]; AISMaxSampleValue: PROC[pa: PixelArray, i: NAT] RETURNS[Val] ~ { data: Data ~ NARROW[pa.data]; max: ARRAY [0..3] OF Val ~ [1B, 3B, 17B, 377B]; IF i IN[0..data.samplesPerPixel) THEN RETURN[max[data.lgBitsPerSample]] ELSE ERROR RuntimeError.BoundsFault; }; AISGetSample: PROC[pa: PixelArray, s, f, i: NAT _ 0] RETURNS[Val] ~ { data: Data ~ NARROW[pa.data]; IF s IN[0..pa.sSize) AND f IN[0..pa.fSize) AND i IN[0..pa.samplesPerPixel) THEN { k: NAT ~ pa.samplesPerPixel*f+i; IF data.sBuffer#s THEN LoadBuffer[data, s]; SELECT data.lgBitsPerSample FROM 3 => RETURN[ORD[data.buffer[k]]]; ENDCASE => ERROR; } ELSE ERROR RuntimeError.BoundsFault; }; AISGetRow: PROC[pa: PixelArray, row: Row, s, f, i: NAT _ 0] ~ { data: Data ~ NARROW[pa.data]; IF s IN[0..pa.sSize) AND f IN[0..pa.fSize) AND i IN[0..pa.samplesPerPixel) THEN { IF pa.samplesPerPixel=1 AND data.lgBitsPerSample=3 THEN { count: NAT ~ MIN[row.size, pa.fSize-f]; stream: STREAM ~ data.stream; index: INT _ data.rasterIndex+data.bytesPerLine*s; IF data.linesPerBlock#0 THEN index _ index+data.bytesPadding*(s/data.linesPerBlock); IO.SetIndex[stream, index]; TRUSTED { base: LONG POINTER ~ LOOPHOLE[row, LONG POINTER]+SIZE[RowRep[0]]; IF IO.UnsafeGetBlock[self: stream, block: [base: base, count: count]]=count THEN NULL ELSE ERROR IO.EndOfStream[stream]; }; } ELSE ERROR; } ELSE ERROR RuntimeError.BoundsFault; }; END.