CentrED/Imaging/ImagingIO.pas

670 lines
19 KiB
Plaintext

{
Vampyre Imaging Library
by Marek Mauder
https://github.com/galfar/imaginglib
https://imaginglib.sourceforge.io
- - - - -
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0.
}
{ This unit contains default IO functions for reading from/writing 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(const IOFunctions: TIOFunctions; Handle: TImagingHandle): Int64;
{ Helper function that initializes TMemoryIORec with given params.}
function PrepareMemIO(Data: Pointer; Size: LongInt): TMemoryIORec;
{ Reads one text line from input (CR+LF, CR, or LF as line delimiter).}
function ReadLine(const IOFunctions: TIOFunctions; Handle: TImagingHandle;
out Line: AnsiString; FailOnControlChars: Boolean = False): Boolean;
{ Writes one text line to input with optional line delimiter.}
procedure WriteLine(const IOFunctions: TIOFunctions; Handle: TImagingHandle;
const Line: AnsiString; const LineEnding: AnsiString = sLineBreak);
type
TReadMemoryStream = class(TCustomMemoryStream)
public
constructor Create(Data: Pointer; Size: Integer);
class function CreateFromIOHandle(const IOFunctions: TIOFunctions; Handle: TImagingHandle): TReadMemoryStream;
end;
TImagingIOStream = class(TStream)
private
FIO: TIOFunctions;
FHandle: TImagingHandle;
public
constructor Create(const IOFunctions: TIOFunctions; Handle: TImagingHandle);
end;
implementation
const
DefaultBufferSize = 16 * 1024;
type
{ Based on TaaBufferedStream
Copyright (c) Julian M Bucknall 1997, 1999 }
TBufferedStream = class
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, soBeginning);
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, soBeginning);
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;
// Copy 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 FileOpen(FileName: PChar; Mode: TOpenMode): TImagingHandle; cdecl;
var
Stream: TStream;
begin
Stream := nil;
case Mode of
omReadOnly: Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
omCreate: Stream := TFileStream.Create(FileName, fmCreate);
omReadWrite:
begin
if FileExists(FileName) then
Stream := TFileStream.Create(FileName, fmOpenReadWrite or fmShareExclusive)
else
Stream := TFileStream.Create(FileName, fmCreate);
end;
end;
Assert(Stream <> nil);
Result := TBufferedStream.Create(Stream);
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: Int64; Mode: TSeekMode): Int64; cdecl;
begin
Result := TBufferedStream(Handle).Seek(Offset, LongInt(Mode));
end;
function FileTell(Handle: TImagingHandle): Int64; 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 StreamOpen(FileName: PChar; Mode: TOpenMode): 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: Int64; Mode: TSeekMode): Int64; cdecl;
begin
Result := TStream(Handle).Seek(Offset, Word(Mode));
end;
function StreamTell(Handle: TImagingHandle): Int64; 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 MemoryOpen(FileName: PChar; Mode: TOpenMode): 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: Int64; Mode: TSeekMode): Int64; 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): Int64; 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(const IOFunctions: TIOFunctions; Handle: TImagingHandle): Int64;
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;
function ReadLine(const IOFunctions: TIOFunctions; Handle: TImagingHandle;
out Line: AnsiString; FailOnControlChars: Boolean): Boolean;
const
MaxLine = 1024;
var
EolPos, Pos: Integer;
C: AnsiChar;
EolReached: Boolean;
Endings: set of AnsiChar;
begin
Line := '';
Pos := 0;
EolPos := 0;
EolReached := False;
Endings := [#10, #13];
Result := True;
while not IOFunctions.Eof(Handle) do
begin
IOFunctions.Read(Handle, @C, SizeOf(C));
if FailOnControlChars and (Byte(C) < $20) then
begin
Break;
end;
if not (C in Endings) then
begin
if EolReached then
begin
IOFunctions.Seek(Handle, EolPos, smFromBeginning);
Exit;
end
else
begin
SetLength(Line, Length(Line) + 1);
Line[Length(Line)] := C;
end;
end
else if not EolReached then
begin
EolReached := True;
EolPos := IOFunctions.Tell(Handle);
end;
Inc(Pos);
if Pos >= MaxLine then
begin
Break;
end;
end;
Result := False;
IOFunctions.Seek(Handle, -Pos, smFromCurrent);
end;
procedure WriteLine(const IOFunctions: TIOFunctions; Handle: TImagingHandle;
const Line: AnsiString; const LineEnding: AnsiString);
var
ToWrite: AnsiString;
begin
ToWrite := Line + LineEnding;
IOFunctions.Write(Handle, @ToWrite[1], Length(ToWrite));
end;
{ TReadMemoryStream }
constructor TReadMemoryStream.Create(Data: Pointer; Size: Integer);
begin
SetPointer(Data, Size);
end;
class function TReadMemoryStream.CreateFromIOHandle(const IOFunctions: TIOFunctions; Handle: TImagingHandle): TReadMemoryStream;
var
Data: Pointer;
Size: Integer;
begin
Size := GetInputSize(IOFunctions, Handle);
GetMem(Data, Size);
IOFunctions.Read(Handle, Data, Size);
Result := TReadMemoryStream.Create(Data, Size);
end;
{ TImagingIOStream }
constructor TImagingIOStream.Create(const IOFunctions: TIOFunctions;
Handle: TImagingHandle);
begin
end;
initialization
OriginalFileIO.Open := FileOpen;
OriginalFileIO.Close := FileClose;
OriginalFileIO.Eof := FileEof;
OriginalFileIO.Seek := FileSeek;
OriginalFileIO.Tell := FileTell;
OriginalFileIO.Read := FileRead;
OriginalFileIO.Write := FileWrite;
StreamIO.Open := StreamOpen;
StreamIO.Close := StreamClose;
StreamIO.Eof := StreamEof;
StreamIO.Seek := StreamSeek;
StreamIO.Tell := StreamTell;
StreamIO.Read := StreamRead;
StreamIO.Write := StreamWrite;
MemoryIO.Open := MemoryOpen;
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.77.3 ---------------------------------------------------
- IO functions now have 64bit sizes and offsets.
- Added helper classes TReadMemoryStream and TImagingIOStream.
-- 0.77.1 ---------------------------------------------------
- Updated IO Open functions according to changes in ImagingTypes.
- Added ReadLine and WriteLine functions.
-- 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/writing. 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.