<> <> <> <> 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; <> <<>> 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] ~ { <> byteIndex: INT ~ IO.GetIndex[stream]; wordIndex _ byteIndex/AISFileFormat.bytesPerWord; }; SetWordIndex: PROC [stream: STREAM, wordIndex: INT] ~ { <> byteIndex: INT ~ wordIndex*AISFileFormat.bytesPerWord; IO.SetIndex[stream, byteIndex]; }; SkipWords: PROC [stream: STREAM, wordCount: INT] ~ { <> byteCount: INT ~ wordCount*AISFileFormat.bytesPerWord; IO.SetIndex[stream, IO.GetIndex[stream]+byteCount]; }; ReadWords: UNSAFE PROC [stream: STREAM, base: LONG POINTER, words: INT] ~ UNCHECKED { <> 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] { 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 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.