<> <> <> <> DIRECTORY AISFileFormat USING [AttributeHeader, byteSizeAttributeHeader, byteSizeCommentPart, byteSizePartHeader, byteSizePhotometryPart, byteSizePlacementPart, byteSizeRasterPart, byteSizeUCACoding, bytesPerAISWord, CommentPart, nil, PartHeader, passwordValue, PhotometryPart, PlacementPart, RasterPart, UCACoding, wordsPerAISPage], Basics USING [BITSHIFT, bitsPerByte, LongMult], FS USING [StreamOpen], ImagerPixelArray USING [Error, Join3, PixelArray], ImagerPixelArrayAISPrivate USING [Data, DataRep], ImagerPixelArrayPrivate USING [GetSamplesProc, MaxSampleValueProc, New, NewClass, PixelArrayClass], ImagerSample USING [GetSamples, NewSampleMap, SampleMap, Vec], ImagerTransformation USING [ScanMode, SFToXY, Transformation], IO USING [EndOfStream, GetIndex, SetIndex, STREAM, UnsafeGetBlock], Rope USING [FromProc, ROPE]; ImagerPixelArrayAISImpl: CEDAR MONITOR LOCKS data USING data: Data IMPORTS Basics, FS, ImagerPixelArray, ImagerPixelArrayPrivate, ImagerSample, ImagerTransformation, IO, Rope EXPORTS ImagerPixelArray ~ BEGIN ROPE: TYPE ~ Rope.ROPE; STREAM: TYPE ~ IO.STREAM; PixelArray: TYPE ~ ImagerPixelArray.PixelArray; Data: TYPE ~ ImagerPixelArrayAISPrivate.Data; DataRep: TYPE ~ ImagerPixelArrayAISPrivate.DataRep; <<>> defaultLinesPerBlock: NAT _ 16; ProduceError: PROC [explanation: ROPE] ~ { ERROR ImagerPixelArray.Error[[code: $invalidAISFile, explanation: explanation]]; }; Assert: PROC [assertion: BOOL] ~ { IF NOT assertion THEN ProduceError["AIS file structure is inconsistent."]; }; BytesForAISWords: PROC [aisWords: CARDINAL] RETURNS [INT] ~ INLINE { RETURN[Basics.LongMult[AISFileFormat.bytesPerAISWord, aisWords]]; }; ReadBytes: UNSAFE PROC [stream: STREAM, base: LONG POINTER, bytes: INT] ~ UNCHECKED { actualBytes: INT ~ IO.UnsafeGetBlock[stream, [base: LOOPHOLE[base], count: bytes]]; IF actualBytes#bytes THEN ERROR IO.EndOfStream[stream]; -- file too short }; FromAIS: PUBLIC PROC [name: ROPE] RETURNS [PixelArray] ~ { OPEN AISFileFormat; stream: STREAM ~ FS.StreamOpen[name]; data: Data ~ NEW[DataRep _ [stream: stream]]; header: AttributeHeader; m: ImagerTransformation.Transformation _ NIL; -- from [s, f] to [x, y] TRUSTED { ReadBytes[stream, @header, byteSizeAttributeHeader] }; IF header.password#passwordValue THEN ProduceError["AIS password value is wrong."]; Assert[header.length>0 AND (header.length MOD wordsPerAISPage)=0]; data.rasterSectionIndex _ BytesForAISWords[header.length]; FOR firstPart: BOOL _ TRUE, FALSE DO partHeader: PartHeader; startIndex: INT ~ IO.GetIndex[stream]; stopIndex: INT; TRUSTED { ReadBytes[stream, @partHeader, byteSizePartHeader] }; stopIndex _ startIndex+BytesForAISWords[partHeader.length]; Assert[stopIndex<=data.rasterSectionIndex]; SELECT partHeader.type FROM nil => { Assert[partHeader.length=0]; EXIT }; -- should be a zero word raster => { raster: RasterPart; Assert[firstPart]; -- raster part must be first TRUSTED { ReadBytes[stream, @raster, byteSizeRasterPart] }; Assert[IO.GetIndex[stream]<=stopIndex]; Assert[raster.scanCount>0 AND raster.scanLength>0 AND raster.samplesPerPixel>0]; data.raster _ NEW[RasterPart _ raster]; SELECT raster.codingType FROM uca => { uca: UCACoding; byteSizeCoding: INT ~ stopIndex-IO.GetIndex[stream]; Assert[byteSizeCoding<=byteSizeUCACoding]; TRUSTED { ReadBytes[stream, @uca, byteSizeCoding] }; IF uca.bitsPerSample=0 THEN uca.bitsPerSample _ 1; -- kludge Assert[INT[uca.bitsPerSample]*INT[raster.samplesPerPixel]*INT[raster.scanLength] <=INT[Basics.bitsPerByte]*BytesForAISWords[uca.wordsPerScanLine]]; IF byteSizeCoding ProduceError["Unknown AIS coding type."]; }; placement => { placement: PlacementPart; TRUSTED { ReadBytes[stream, @placement, byteSizePlacementPart] }; Assert[IO.GetIndex[stream]=stopIndex]; 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]; Assert[data.placement=NIL]; data.placement _ NEW[PlacementPart _ placement]; }; photometry => { photometry: PhotometryPart; TRUSTED { ReadBytes[stream, @photometry, byteSizePhotometryPart] }; Assert[IO.GetIndex[stream]<=stopIndex]; IO.SetIndex[stream, stopIndex]; -- Ignore histogram, if any Assert[data.photometry=NIL]; data.photometry _ NEW[PhotometryPart _ photometry]; }; comment => { comment: CommentPart; byteSizeComment: INT ~ stopIndex-IO.GetIndex[stream]; length: NAT _ 0; -- number of characters in the string rope: ROPE _ NIL; Assert[byteSizeComment IN[1..byteSizeCommentPart]]; TRUSTED { ReadBytes[stream, @comment, byteSizeComment] }; length _ ORD[comment[0]]; Assert[byteSizeComment>=length+1]; { -- 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 => IO.SetIndex[stream, stopIndex]; -- ignore unknown part type IF IO.GetIndex[stream]=stopIndex THEN NULL ELSE ERROR ImagerPixelArray.Error[[code: $bug, explanation: "Bug in FromAIS."]]; ENDLOOP; IF data.raster#NIL AND data.uca#NIL THEN { samplesPerPixel: NAT ~ data.raster.samplesPerPixel; sSize: CARDINAL ~ data.raster.scanCount; fSize: CARDINAL ~ data.raster.scanLength; linesPerBlock: CARDINAL _ data.uca.scanLinesPerBlock; bytesPadding: INT _ 0; scanMode: ImagerTransformation.ScanMode; data.bytesPerLine _ BytesForAISWords[data.uca.wordsPerScanLine]; IF linesPerBlock=nil THEN linesPerBlock _ MIN[defaultLinesPerBlock, sSize] ELSE bytesPadding _ BytesForAISWords[data.uca.paddingPerBlock]; data.bytesPerBlock _ data.bytesPerLine*linesPerBlock+bytesPadding; data.buffer _ ImagerSample.NewSampleMap[ size: [s: linesPerBlock, f: Basics.LongMult[samplesPerPixel, fSize]], bitsPerSample: data.uca.bitsPerSample, bitsPerLine: Basics.bitsPerByte*data.bytesPerLine ]; SELECT data.raster.scanDirection FROM 0, 8 => scanMode _ [slow: right, fast: up]; 3 => scanMode _ [slow: down, fast: right]; ENDCASE => ERROR ImagerPixelArray.Error[[$unimplementedAISFile, "AIS file has unimplemented scanDirection"]]; RETURN[ImagerPixelArrayPrivate.New[class: classAIS, data: data, immutable: TRUE, samplesPerPixel: samplesPerPixel, sSize: sSize, fSize: fSize, m: ImagerTransformation.SFToXY[scanMode: scanMode, sSize: sSize, fSize: fSize] ]]; } ELSE { ProduceError["AIS file has no raster part."]; RETURN[NIL] }; }; classAIS: ImagerPixelArrayPrivate.PixelArrayClass ~ ImagerPixelArrayPrivate.NewClass[ type: $AIS, MaxSampleValue: MaxSampleValueAIS, GetSamples: GetSamplesAIS ]; MaxSampleValueAIS: ImagerPixelArrayPrivate.MaxSampleValueProc ~ { <<-- PROC [self: PixelArray, i: NAT] RETURNS [Sample] -->> data: Data ~ NARROW[self.data]; RETURN[Basics.BITSHIFT[1, data.uca.bitsPerSample]-1]; }; GetSamplesAIS: ImagerPixelArrayPrivate.GetSamplesProc ~ { <<-- PROC [self: PixelArray, i: NAT, s, f: INT, samples: SampleBuffer, start: NAT, count: NAT] -->> data: Data ~ NARROW[self.data]; entryGetSamplesAIS: ENTRY PROC [data: Data] ~ { ENABLE UNWIND => NULL; s0: CARDINAL ~ s; f0: CARDINAL ~ f; samplesPerPixel: CARDINAL ~ data.raster.samplesPerPixel; buffer: ImagerSample.SampleMap ~ data.buffer; IF s0 NOT IN[data.smin..data.smax) THEN { linesPerBlock: NAT ~ buffer.size.s; block: NAT ~ s0/linesPerBlock; -- block number containing s0 byteIndex: INT ~ data.rasterSectionIndex+block*data.bytesPerBlock; byteCount: INT _ 0; data.smin _ block*linesPerBlock; -- first scan line in block data.smax _ MIN[data.smin+linesPerBlock, data.raster.scanCount]; byteCount _ (data.smax-data.smin)*data.bytesPerLine; IO.SetIndex[self: data.stream, index: byteIndex]; TRUSTED { ReadBytes[stream: data.stream, base: buffer.base.word, bytes: byteCount] }; }; ImagerSample.GetSamples[self: buffer, min: [s: s0-data.smin, f: f0*samplesPerPixel+i], delta: [s: 0, f: samplesPerPixel], samples: samples, start: start, count: count ]; }; entryGetSamplesAIS[data]; }; Join3AIS: PUBLIC PROC [name1, name2, name3: ROPE] RETURNS [PixelArray] ~ { RETURN[ImagerPixelArray.Join3[FromAIS[name1], FromAIS[name2], FromAIS[name3]]]; }; END.