{ $Id: ImagingIO.pas 100 2007-06-28 21:09:52Z galfar $ Vampyre Imaging Library by Marek Mauder http://imaginglib.sourceforge.net The contents of this file are used with permission, subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/MPL-1.1.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. Alternatively, the contents of this file may be used under the terms of the GNU Lesser General Public License (the "LGPL License"), in which case the provisions of the LGPL License are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the LGPL License and not to allow others to use your version of this file under the MPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the LGPL License. If you do not delete the provisions above, a recipient may use your version of this file under either the MPL or the LGPL License. For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html } { This unit contains default IO functions for reading from/writting to files, streams and memory.} unit ImagingIO; {$I ImagingOptions.inc} interface uses SysUtils, Classes, ImagingTypes, Imaging, ImagingUtility; type TMemoryIORec = record Data: ImagingUtility.PByteArray; Position: LongInt; Size: LongInt; end; PMemoryIORec = ^TMemoryIORec; var OriginalFileIO: TIOFunctions; FileIO: TIOFunctions; StreamIO: TIOFunctions; MemoryIO: TIOFunctions; { Helper function that returns size of input (from current position to the end) represented by Handle (and opened and operated on by members of IOFunctions).} function GetInputSize(IOFunctions: TIOFunctions; Handle: TImagingHandle): LongInt; { Helper function that initializes TMemoryIORec with given params.} function PrepareMemIO(Data: Pointer; Size: LongInt): TMemoryIORec; implementation const DefaultBufferSize = 16 * 1024; type { Based on TaaBufferedStream Copyright (c) Julian M Bucknall 1997, 1999 } TBufferedStream = class(TObject) private FBuffer: PByteArray; FBufSize: Integer; FBufStart: Integer; FBufPos: Integer; FBytesInBuf: Integer; FSize: Integer; FDirty: Boolean; FStream: TStream; function GetPosition: Integer; function GetSize: Integer; procedure ReadBuffer; procedure WriteBuffer; procedure SetPosition(const Value: Integer); public constructor Create(AStream: TStream); destructor Destroy; override; function Read(var Buffer; Count: Integer): Integer; function Write(const Buffer; Count: Integer): Integer; function Seek(Offset: Integer; Origin: Word): Integer; procedure Commit; property Stream: TStream read FStream; property Position: Integer read GetPosition write SetPosition; property Size: Integer read GetSize; end; constructor TBufferedStream.Create(AStream: TStream); begin inherited Create; FStream := AStream; FBufSize := DefaultBufferSize; GetMem(FBuffer, FBufSize); FBufPos := 0; FBytesInBuf := 0; FBufStart := 0; FDirty := False; FSize := AStream.Size; end; destructor TBufferedStream.Destroy; begin if FBuffer <> nil then begin Commit; FreeMem(FBuffer); end; FStream.Position := Position; // Make sure source stream has right position inherited Destroy; end; function TBufferedStream.GetPosition: Integer; begin Result := FBufStart + FBufPos; end; procedure TBufferedStream.SetPosition(const Value: Integer); begin Seek(Value, soFromCurrent); end; function TBufferedStream.GetSize: Integer; begin Result := FSize; end; procedure TBufferedStream.ReadBuffer; var SeekResult: Integer; begin SeekResult := FStream.Seek(FBufStart, 0); if SeekResult = -1 then raise Exception.Create('TBufferedStream.ReadBuffer: seek failed'); FBytesInBuf := FStream.Read(FBuffer^, FBufSize); if FBytesInBuf <= 0 then raise Exception.Create('TBufferedStream.ReadBuffer: read failed'); end; procedure TBufferedStream.WriteBuffer; var SeekResult: Integer; BytesWritten: Integer; begin SeekResult := FStream.Seek(FBufStart, 0); if SeekResult = -1 then raise Exception.Create('TBufferedStream.WriteBuffer: seek failed'); BytesWritten := FStream.Write(FBuffer^, FBytesInBuf); if BytesWritten <> FBytesInBuf then raise Exception.Create('TBufferedStream.WriteBuffer: write failed'); end; procedure TBufferedStream.Commit; begin if FDirty then begin WriteBuffer; FDirty := False; end; end; function TBufferedStream.Read(var Buffer; Count: Integer): Integer; var BufAsBytes : TByteArray absolute Buffer; BufIdx, BytesToGo, BytesToRead: Integer; begin // Calculate the actual number of bytes we can read - this depends on // the current position and size of the stream as well as the number // of bytes requested. BytesToGo := Count; if FSize < (FBufStart + FBufPos + Count) then BytesToGo := FSize - (FBufStart + FBufPos); if BytesToGo <= 0 then begin Result := 0; Exit; end; // Remember to return the result of our calculation Result := BytesToGo; BufIdx := 0; if FBytesInBuf = 0 then ReadBuffer; // Calculate the number of bytes we can read prior to the loop BytesToRead := FBytesInBuf - FBufPos; if BytesToRead > BytesToGo then BytesToRead := BytesToGo; // Copy from the stream buffer to the caller's buffer Move(FBuffer^[FBufPos], BufAsBytes[BufIdx], BytesToRead); // Calculate the number of bytes still to read} Dec(BytesToGo, BytesToRead); // while we have bytes to read, read them while BytesToGo > 0 do begin Inc(BufIdx, BytesToRead); // As we've exhausted this buffer-full, advance to the next, check // to see whether we need to write the buffer out first if FDirty then begin WriteBuffer; FDirty := false; end; Inc(FBufStart, FBufSize); FBufPos := 0; ReadBuffer; // Calculate the number of bytes we can read in this cycle BytesToRead := FBytesInBuf; if BytesToRead > BytesToGo then BytesToRead := BytesToGo; // Ccopy from the stream buffer to the caller's buffer Move(FBuffer^, BufAsBytes[BufIdx], BytesToRead); // Calculate the number of bytes still to read Dec(BytesToGo, BytesToRead); end; // Remember our new position Inc(FBufPos, BytesToRead); if FBufPos = FBufSize then begin Inc(FBufStart, FBufSize); FBufPos := 0; FBytesInBuf := 0; end; end; function TBufferedStream.Seek(Offset: Integer; Origin: Word): Integer; var NewBufStart, NewPos: Integer; begin // Calculate the new position case Origin of soFromBeginning : NewPos := Offset; soFromCurrent : NewPos := FBufStart + FBufPos + Offset; soFromEnd : NewPos := FSize + Offset; else raise Exception.Create('TBufferedStream.Seek: invalid origin'); end; if (NewPos < 0) or (NewPos > FSize) then begin //NewPos := ClampInt(NewPos, 0, FSize); don't do this - for writing end; // Calculate which page of the file we need to be at NewBufStart := NewPos and not Pred(FBufSize); // If the new page is different than the old, mark the buffer as being // ready to be replenished, and if need be write out any dirty data if NewBufStart <> FBufStart then begin if FDirty then begin WriteBuffer; FDirty := False; end; FBufStart := NewBufStart; FBytesInBuf := 0; end; // Save the new position FBufPos := NewPos - NewBufStart; Result := NewPos; end; function TBufferedStream.Write(const Buffer; Count: Integer): Integer; var BufAsBytes: TByteArray absolute Buffer; BufIdx, BytesToGo, BytesToWrite: Integer; begin // When we write to this stream we always assume that we can write the // requested number of bytes: if we can't (eg, the disk is full) we'll // get an exception somewhere eventually. BytesToGo := Count; // Remember to return the result of our calculation Result := BytesToGo; BufIdx := 0; if (FBytesInBuf = 0) and (FSize > FBufStart) then ReadBuffer; // Calculate the number of bytes we can write prior to the loop BytesToWrite := FBufSize - FBufPos; if BytesToWrite > BytesToGo then BytesToWrite := BytesToGo; // Copy from the caller's buffer to the stream buffer Move(BufAsBytes[BufIdx], FBuffer^[FBufPos], BytesToWrite); // Mark our stream buffer as requiring a save to the actual stream, // note that this will suffice for the rest of the routine as well: no // inner routine will turn off the dirty flag. FDirty := True; // Calculate the number of bytes still to write Dec(BytesToGo, BytesToWrite); // While we have bytes to write, write them while BytesToGo > 0 do begin Inc(BufIdx, BytesToWrite); // As we've filled this buffer, write it out to the actual stream // and advance to the next buffer, reading it if required FBytesInBuf := FBufSize; WriteBuffer; Inc(FBufStart, FBufSize); FBufPos := 0; FBytesInBuf := 0; if FSize > FBufStart then ReadBuffer; // Calculate the number of bytes we can write in this cycle BytesToWrite := FBufSize; if BytesToWrite > BytesToGo then BytesToWrite := BytesToGo; // Copy from the caller's buffer to our buffer Move(BufAsBytes[BufIdx], FBuffer^, BytesToWrite); // Calculate the number of bytes still to write Dec(BytesToGo, BytesToWrite); end; // Remember our new position Inc(FBufPos, BytesToWrite); // Make sure the count of valid bytes is correct if FBytesInBuf < FBufPos then FBytesInBuf := FBufPos; // Make sure the stream size is correct if FSize < (FBufStart + FBytesInBuf) then FSize := FBufStart + FBytesInBuf; // If we're at the end of the buffer, write it out and advance to the // start of the next page if FBufPos = FBufSize then begin WriteBuffer; FDirty := False; Inc(FBufStart, FBufSize); FBufPos := 0; FBytesInBuf := 0; end; end; { File IO functions } function FileOpenRead(FileName: PChar): TImagingHandle; cdecl; begin Result := TBufferedStream.Create(TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite)); end; function FileOpenWrite(FileName: PChar): TImagingHandle; cdecl; begin Result := TBufferedStream.Create(TFileStream.Create(FileName, fmCreate or fmShareDenyWrite)); end; procedure FileClose(Handle: TImagingHandle); cdecl; var Stream: TStream; begin Stream := TBufferedStream(Handle).Stream; TBufferedStream(Handle).Free; Stream.Free; end; function FileEof(Handle: TImagingHandle): Boolean; cdecl; begin Result := TBufferedStream(Handle).Position = TBufferedStream(Handle).Size; end; function FileSeek(Handle: TImagingHandle; Offset: LongInt; Mode: TSeekMode): LongInt; cdecl; begin Result := TBufferedStream(Handle).Seek(Offset, LongInt(Mode)); end; function FileTell(Handle: TImagingHandle): LongInt; cdecl; begin Result := TBufferedStream(Handle).Position; end; function FileRead(Handle: TImagingHandle; Buffer: Pointer; Count: LongInt): LongInt; cdecl; begin Result := TBufferedStream(Handle).Read(Buffer^, Count); end; function FileWrite(Handle: TImagingHandle; Buffer: Pointer; Count: LongInt): LongInt; cdecl; begin Result := TBufferedStream(Handle).Write(Buffer^, Count); end; { Stream IO functions } function StreamOpenRead(FileName: PChar): TImagingHandle; cdecl; begin Result := FileName; end; function StreamOpenWrite(FileName: PChar): TImagingHandle; cdecl; begin Result := FileName; end; procedure StreamClose(Handle: TImagingHandle); cdecl; begin end; function StreamEof(Handle: TImagingHandle): Boolean; cdecl; begin Result := TStream(Handle).Position = TStream(Handle).Size; end; function StreamSeek(Handle: TImagingHandle; Offset: LongInt; Mode: TSeekMode): LongInt; cdecl; begin Result := TStream(Handle).Seek(Offset, LongInt(Mode)); end; function StreamTell(Handle: TImagingHandle): LongInt; cdecl; begin Result := TStream(Handle).Position; end; function StreamRead(Handle: TImagingHandle; Buffer: Pointer; Count: LongInt): LongInt; cdecl; begin Result := TStream(Handle).Read(Buffer^, Count); end; function StreamWrite(Handle: TImagingHandle; Buffer: Pointer; Count: LongInt): LongInt; cdecl; begin Result := TStream(Handle).Write(Buffer^, Count); end; { Memory IO functions } function MemoryOpenRead(FileName: PChar): TImagingHandle; cdecl; begin Result := FileName; end; function MemoryOpenWrite(FileName: PChar): TImagingHandle; cdecl; begin Result := FileName; end; procedure MemoryClose(Handle: TImagingHandle); cdecl; begin end; function MemoryEof(Handle: TImagingHandle): Boolean; cdecl; begin Result := PMemoryIORec(Handle).Position = PMemoryIORec(Handle).Size; end; function MemorySeek(Handle: TImagingHandle; Offset: LongInt; Mode: TSeekMode): LongInt; cdecl; begin Result := PMemoryIORec(Handle).Position; case Mode of smFromBeginning: Result := Offset; smFromCurrent: Result := PMemoryIORec(Handle).Position + Offset; smFromEnd: Result := PMemoryIORec(Handle).Size + Offset; end; //Result := ClampInt(Result, 0, PMemoryIORec(Handle).Size); don't do this - some file formats use it PMemoryIORec(Handle).Position := Result; end; function MemoryTell(Handle: TImagingHandle): LongInt; cdecl; begin Result := PMemoryIORec(Handle).Position; end; function MemoryRead(Handle: TImagingHandle; Buffer: Pointer; Count: LongInt): LongInt; cdecl; var Rec: PMemoryIORec; begin Rec := PMemoryIORec(Handle); Result := Count; if Rec.Position + Count > Rec.Size then Result := Rec.Size - Rec.Position; Move(Rec.Data[Rec.Position], Buffer^, Result); Rec.Position := Rec.Position + Result; end; function MemoryWrite(Handle: TImagingHandle; Buffer: Pointer; Count: LongInt): LongInt; cdecl; var Rec: PMemoryIORec; begin Rec := PMemoryIORec(Handle); Result := Count; if Rec.Position + Count > Rec.Size then Result := Rec.Size - Rec.Position; Move(Buffer^, Rec.Data[Rec.Position], Result); Rec.Position := Rec.Position + Result; end; { Helper IO functions } function GetInputSize(IOFunctions: TIOFunctions; Handle: TImagingHandle): LongInt; var OldPos: Int64; begin OldPos := IOFunctions.Tell(Handle); IOFunctions.Seek(Handle, 0, smFromEnd); Result := IOFunctions.Tell(Handle); IOFunctions.Seek(Handle, OldPos, smFromBeginning); end; function PrepareMemIO(Data: Pointer; Size: LongInt): TMemoryIORec; begin Result.Data := Data; Result.Position := 0; Result.Size := Size; end; initialization OriginalFileIO.OpenRead := FileOpenRead; OriginalFileIO.OpenWrite := FileOpenWrite; OriginalFileIO.Close := FileClose; OriginalFileIO.Eof := FileEof; OriginalFileIO.Seek := FileSeek; OriginalFileIO.Tell := FileTell; OriginalFileIO.Read := FileRead; OriginalFileIO.Write := FileWrite; StreamIO.OpenRead := StreamOpenRead; StreamIO.OpenWrite := StreamOpenWrite; StreamIO.Close := StreamClose; StreamIO.Eof := StreamEof; StreamIO.Seek := StreamSeek; StreamIO.Tell := StreamTell; StreamIO.Read := StreamRead; StreamIO.Write := StreamWrite; MemoryIO.OpenRead := MemoryOpenRead; MemoryIO.OpenWrite := MemoryOpenWrite; MemoryIO.Close := MemoryClose; MemoryIO.Eof := MemoryEof; MemoryIO.Seek := MemorySeek; MemoryIO.Tell := MemoryTell; MemoryIO.Read := MemoryRead; MemoryIO.Write := MemoryWrite; ResetFileIO; { File Notes: -- TODOS ---------------------------------------------------- - nothing now -- 0.23 Changes/Bug Fixes ----------------------------------- - Added merge between buffered read-only and write-only file stream adapters - TIFF saving needed both reading and writing. - Fixed bug causing wrong value of TBufferedWriteFile.Size (needed to add buffer pos to size). -- 0.21 Changes/Bug Fixes ----------------------------------- - Removed TMemoryIORec.Written, use Position to get proper memory position (Written didn't take Seeks into account). - Added TBufferedReadFile and TBufferedWriteFile classes for buffered file reading/writting. File IO functions now use these classes resulting in performance increase mainly in file formats that read/write many small chunks. - Added fmShareDenyWrite to FileOpenRead. You can now read files opened for reading by Imaging from other apps. - Added GetInputSize and PrepareMemIO helper functions. -- 0.19 Changes/Bug Fixes ----------------------------------- - changed behaviour of MemorySeek to act as TStream based Seeks } end.