575 lines
16 KiB
Plaintext
575 lines
16 KiB
Plaintext
{
|
|
$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.
|
|
|