DEADSOFTWARE

added Vampyre Imaging Library; now textures can be in various formats, including...
[d2df-sdl.git] / src / lib / vampimg / ImagingIO.pas
1 {
2 $Id: ImagingIO.pas 100 2007-06-28 21:09:52Z galfar $
3 Vampyre Imaging Library
4 by Marek Mauder
5 http://imaginglib.sourceforge.net
7 The contents of this file are used with permission, subject to the Mozilla
8 Public License Version 1.1 (the "License"); you may not use this file except
9 in compliance with the License. You may obtain a copy of the License at
10 http://www.mozilla.org/MPL/MPL-1.1.html
12 Software distributed under the License is distributed on an "AS IS" basis,
13 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
14 the specific language governing rights and limitations under the License.
16 Alternatively, the contents of this file may be used under the terms of the
17 GNU Lesser General Public License (the "LGPL License"), in which case the
18 provisions of the LGPL License are applicable instead of those above.
19 If you wish to allow use of your version of this file only under the terms
20 of the LGPL License and not to allow others to use your version of this file
21 under the MPL, indicate your decision by deleting the provisions above and
22 replace them with the notice and other provisions required by the LGPL
23 License. If you do not delete the provisions above, a recipient may use
24 your version of this file under either the MPL or the LGPL License.
26 For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
27 }
29 { This unit contains default IO functions for reading from/writting to
30 files, streams and memory.}
31 unit ImagingIO;
33 {$I ImagingOptions.inc}
35 interface
37 uses
38 SysUtils, Classes, ImagingTypes, Imaging, ImagingUtility;
40 type
41 TMemoryIORec = record
42 Data: ImagingUtility.PByteArray;
43 Position: LongInt;
44 Size: LongInt;
45 end;
46 PMemoryIORec = ^TMemoryIORec;
48 var
49 OriginalFileIO: TIOFunctions;
50 FileIO: TIOFunctions;
51 StreamIO: TIOFunctions;
52 MemoryIO: TIOFunctions;
54 { Helper function that returns size of input (from current position to the end)
55 represented by Handle (and opened and operated on by members of IOFunctions).}
56 function GetInputSize(IOFunctions: TIOFunctions; Handle: TImagingHandle): LongInt;
57 { Helper function that initializes TMemoryIORec with given params.}
58 function PrepareMemIO(Data: Pointer; Size: LongInt): TMemoryIORec;
60 implementation
62 const
63 DefaultBufferSize = 16 * 1024;
65 type
66 { Based on TaaBufferedStream
67 Copyright (c) Julian M Bucknall 1997, 1999 }
68 TBufferedStream = class(TObject)
69 private
70 FBuffer: PByteArray;
71 FBufSize: Integer;
72 FBufStart: Integer;
73 FBufPos: Integer;
74 FBytesInBuf: Integer;
75 FSize: Integer;
76 FDirty: Boolean;
77 FStream: TStream;
78 function GetPosition: Integer;
79 function GetSize: Integer;
80 procedure ReadBuffer;
81 procedure WriteBuffer;
82 procedure SetPosition(const Value: Integer);
83 public
84 constructor Create(AStream: TStream);
85 destructor Destroy; override;
86 function Read(var Buffer; Count: Integer): Integer;
87 function Write(const Buffer; Count: Integer): Integer;
88 function Seek(Offset: Integer; Origin: Word): Integer;
89 procedure Commit;
90 property Stream: TStream read FStream;
91 property Position: Integer read GetPosition write SetPosition;
92 property Size: Integer read GetSize;
93 end;
95 constructor TBufferedStream.Create(AStream: TStream);
96 begin
97 inherited Create;
98 FStream := AStream;
99 FBufSize := DefaultBufferSize;
100 GetMem(FBuffer, FBufSize);
101 FBufPos := 0;
102 FBytesInBuf := 0;
103 FBufStart := 0;
104 FDirty := False;
105 FSize := AStream.Size;
106 end;
108 destructor TBufferedStream.Destroy;
109 begin
110 if FBuffer <> nil then
111 begin
112 Commit;
113 FreeMem(FBuffer);
114 end;
115 FStream.Position := Position; // Make sure source stream has right position
116 inherited Destroy;
117 end;
119 function TBufferedStream.GetPosition: Integer;
120 begin
121 Result := FBufStart + FBufPos;
122 end;
124 procedure TBufferedStream.SetPosition(const Value: Integer);
125 begin
126 Seek(Value, soFromCurrent);
127 end;
129 function TBufferedStream.GetSize: Integer;
130 begin
131 Result := FSize;
132 end;
134 procedure TBufferedStream.ReadBuffer;
135 var
136 SeekResult: Integer;
137 begin
138 SeekResult := FStream.Seek(FBufStart, 0);
139 if SeekResult = -1 then
140 raise Exception.Create('TBufferedStream.ReadBuffer: seek failed');
141 FBytesInBuf := FStream.Read(FBuffer^, FBufSize);
142 if FBytesInBuf <= 0 then
143 raise Exception.Create('TBufferedStream.ReadBuffer: read failed');
144 end;
146 procedure TBufferedStream.WriteBuffer;
147 var
148 SeekResult: Integer;
149 BytesWritten: Integer;
150 begin
151 SeekResult := FStream.Seek(FBufStart, 0);
152 if SeekResult = -1 then
153 raise Exception.Create('TBufferedStream.WriteBuffer: seek failed');
154 BytesWritten := FStream.Write(FBuffer^, FBytesInBuf);
155 if BytesWritten <> FBytesInBuf then
156 raise Exception.Create('TBufferedStream.WriteBuffer: write failed');
157 end;
159 procedure TBufferedStream.Commit;
160 begin
161 if FDirty then
162 begin
163 WriteBuffer;
164 FDirty := False;
165 end;
166 end;
168 function TBufferedStream.Read(var Buffer; Count: Integer): Integer;
169 var
170 BufAsBytes : TByteArray absolute Buffer;
171 BufIdx, BytesToGo, BytesToRead: Integer;
172 begin
173 // Calculate the actual number of bytes we can read - this depends on
174 // the current position and size of the stream as well as the number
175 // of bytes requested.
176 BytesToGo := Count;
177 if FSize < (FBufStart + FBufPos + Count) then
178 BytesToGo := FSize - (FBufStart + FBufPos);
180 if BytesToGo <= 0 then
181 begin
182 Result := 0;
183 Exit;
184 end;
185 // Remember to return the result of our calculation
186 Result := BytesToGo;
188 BufIdx := 0;
189 if FBytesInBuf = 0 then
190 ReadBuffer;
191 // Calculate the number of bytes we can read prior to the loop
192 BytesToRead := FBytesInBuf - FBufPos;
193 if BytesToRead > BytesToGo then
194 BytesToRead := BytesToGo;
195 // Copy from the stream buffer to the caller's buffer
196 Move(FBuffer^[FBufPos], BufAsBytes[BufIdx], BytesToRead);
197 // Calculate the number of bytes still to read}
198 Dec(BytesToGo, BytesToRead);
200 // while we have bytes to read, read them
201 while BytesToGo > 0 do
202 begin
203 Inc(BufIdx, BytesToRead);
204 // As we've exhausted this buffer-full, advance to the next, check
205 // to see whether we need to write the buffer out first
206 if FDirty then
207 begin
208 WriteBuffer;
209 FDirty := false;
210 end;
211 Inc(FBufStart, FBufSize);
212 FBufPos := 0;
213 ReadBuffer;
214 // Calculate the number of bytes we can read in this cycle
215 BytesToRead := FBytesInBuf;
216 if BytesToRead > BytesToGo then
217 BytesToRead := BytesToGo;
218 // Ccopy from the stream buffer to the caller's buffer
219 Move(FBuffer^, BufAsBytes[BufIdx], BytesToRead);
220 // Calculate the number of bytes still to read
221 Dec(BytesToGo, BytesToRead);
222 end;
223 // Remember our new position
224 Inc(FBufPos, BytesToRead);
225 if FBufPos = FBufSize then
226 begin
227 Inc(FBufStart, FBufSize);
228 FBufPos := 0;
229 FBytesInBuf := 0;
230 end;
231 end;
233 function TBufferedStream.Seek(Offset: Integer; Origin: Word): Integer;
234 var
235 NewBufStart, NewPos: Integer;
236 begin
237 // Calculate the new position
238 case Origin of
239 soFromBeginning : NewPos := Offset;
240 soFromCurrent : NewPos := FBufStart + FBufPos + Offset;
241 soFromEnd : NewPos := FSize + Offset;
242 else
243 raise Exception.Create('TBufferedStream.Seek: invalid origin');
244 end;
246 if (NewPos < 0) or (NewPos > FSize) then
247 begin
248 //NewPos := ClampInt(NewPos, 0, FSize); don't do this - for writing
249 end;
250 // Calculate which page of the file we need to be at
251 NewBufStart := NewPos and not Pred(FBufSize);
252 // If the new page is different than the old, mark the buffer as being
253 // ready to be replenished, and if need be write out any dirty data
254 if NewBufStart <> FBufStart then
255 begin
256 if FDirty then
257 begin
258 WriteBuffer;
259 FDirty := False;
260 end;
261 FBufStart := NewBufStart;
262 FBytesInBuf := 0;
263 end;
264 // Save the new position
265 FBufPos := NewPos - NewBufStart;
266 Result := NewPos;
267 end;
269 function TBufferedStream.Write(const Buffer; Count: Integer): Integer;
270 var
271 BufAsBytes: TByteArray absolute Buffer;
272 BufIdx, BytesToGo, BytesToWrite: Integer;
273 begin
274 // When we write to this stream we always assume that we can write the
275 // requested number of bytes: if we can't (eg, the disk is full) we'll
276 // get an exception somewhere eventually.
277 BytesToGo := Count;
278 // Remember to return the result of our calculation
279 Result := BytesToGo;
281 BufIdx := 0;
282 if (FBytesInBuf = 0) and (FSize > FBufStart) then
283 ReadBuffer;
284 // Calculate the number of bytes we can write prior to the loop
285 BytesToWrite := FBufSize - FBufPos;
286 if BytesToWrite > BytesToGo then
287 BytesToWrite := BytesToGo;
288 // Copy from the caller's buffer to the stream buffer
289 Move(BufAsBytes[BufIdx], FBuffer^[FBufPos], BytesToWrite);
290 // Mark our stream buffer as requiring a save to the actual stream,
291 // note that this will suffice for the rest of the routine as well: no
292 // inner routine will turn off the dirty flag.
293 FDirty := True;
294 // Calculate the number of bytes still to write
295 Dec(BytesToGo, BytesToWrite);
297 // While we have bytes to write, write them
298 while BytesToGo > 0 do
299 begin
300 Inc(BufIdx, BytesToWrite);
301 // As we've filled this buffer, write it out to the actual stream
302 // and advance to the next buffer, reading it if required
303 FBytesInBuf := FBufSize;
304 WriteBuffer;
305 Inc(FBufStart, FBufSize);
306 FBufPos := 0;
307 FBytesInBuf := 0;
308 if FSize > FBufStart then
309 ReadBuffer;
310 // Calculate the number of bytes we can write in this cycle
311 BytesToWrite := FBufSize;
312 if BytesToWrite > BytesToGo then
313 BytesToWrite := BytesToGo;
314 // Copy from the caller's buffer to our buffer
315 Move(BufAsBytes[BufIdx], FBuffer^, BytesToWrite);
316 // Calculate the number of bytes still to write
317 Dec(BytesToGo, BytesToWrite);
318 end;
319 // Remember our new position
320 Inc(FBufPos, BytesToWrite);
321 // Make sure the count of valid bytes is correct
322 if FBytesInBuf < FBufPos then
323 FBytesInBuf := FBufPos;
324 // Make sure the stream size is correct
325 if FSize < (FBufStart + FBytesInBuf) then
326 FSize := FBufStart + FBytesInBuf;
327 // If we're at the end of the buffer, write it out and advance to the
328 // start of the next page
329 if FBufPos = FBufSize then
330 begin
331 WriteBuffer;
332 FDirty := False;
333 Inc(FBufStart, FBufSize);
334 FBufPos := 0;
335 FBytesInBuf := 0;
336 end;
337 end;
339 { File IO functions }
341 function FileOpenRead(FileName: PChar): TImagingHandle; cdecl;
342 begin
343 Result := TBufferedStream.Create(TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite));
344 end;
346 function FileOpenWrite(FileName: PChar): TImagingHandle; cdecl;
347 begin
348 Result := TBufferedStream.Create(TFileStream.Create(FileName, fmCreate or fmShareDenyWrite));
349 end;
351 procedure FileClose(Handle: TImagingHandle); cdecl;
352 var
353 Stream: TStream;
354 begin
355 Stream := TBufferedStream(Handle).Stream;
356 TBufferedStream(Handle).Free;
357 Stream.Free;
358 end;
360 function FileEof(Handle: TImagingHandle): Boolean; cdecl;
361 begin
362 Result := TBufferedStream(Handle).Position = TBufferedStream(Handle).Size;
363 end;
365 function FileSeek(Handle: TImagingHandle; Offset: LongInt; Mode: TSeekMode):
366 LongInt; cdecl;
367 begin
368 Result := TBufferedStream(Handle).Seek(Offset, LongInt(Mode));
369 end;
371 function FileTell(Handle: TImagingHandle): LongInt; cdecl;
372 begin
373 Result := TBufferedStream(Handle).Position;
374 end;
376 function FileRead(Handle: TImagingHandle; Buffer: Pointer; Count: LongInt):
377 LongInt; cdecl;
378 begin
379 Result := TBufferedStream(Handle).Read(Buffer^, Count);
380 end;
382 function FileWrite(Handle: TImagingHandle; Buffer: Pointer; Count: LongInt):
383 LongInt; cdecl;
384 begin
385 Result := TBufferedStream(Handle).Write(Buffer^, Count);
386 end;
388 { Stream IO functions }
390 function StreamOpenRead(FileName: PChar): TImagingHandle; cdecl;
391 begin
392 Result := FileName;
393 end;
395 function StreamOpenWrite(FileName: PChar): TImagingHandle; cdecl;
396 begin
397 Result := FileName;
398 end;
400 procedure StreamClose(Handle: TImagingHandle); cdecl;
401 begin
402 end;
404 function StreamEof(Handle: TImagingHandle): Boolean; cdecl;
405 begin
406 Result := TStream(Handle).Position = TStream(Handle).Size;
407 end;
409 function StreamSeek(Handle: TImagingHandle; Offset: LongInt; Mode: TSeekMode):
410 LongInt; cdecl;
411 begin
412 Result := TStream(Handle).Seek(Offset, LongInt(Mode));
413 end;
415 function StreamTell(Handle: TImagingHandle): LongInt; cdecl;
416 begin
417 Result := TStream(Handle).Position;
418 end;
420 function StreamRead(Handle: TImagingHandle; Buffer: Pointer; Count: LongInt):
421 LongInt; cdecl;
422 begin
423 Result := TStream(Handle).Read(Buffer^, Count);
424 end;
426 function StreamWrite(Handle: TImagingHandle; Buffer: Pointer; Count: LongInt):
427 LongInt; cdecl;
428 begin
429 Result := TStream(Handle).Write(Buffer^, Count);
430 end;
432 { Memory IO functions }
434 function MemoryOpenRead(FileName: PChar): TImagingHandle; cdecl;
435 begin
436 Result := FileName;
437 end;
439 function MemoryOpenWrite(FileName: PChar): TImagingHandle; cdecl;
440 begin
441 Result := FileName;
442 end;
444 procedure MemoryClose(Handle: TImagingHandle); cdecl;
445 begin
446 end;
448 function MemoryEof(Handle: TImagingHandle): Boolean; cdecl;
449 begin
450 Result := PMemoryIORec(Handle).Position = PMemoryIORec(Handle).Size;
451 end;
453 function MemorySeek(Handle: TImagingHandle; Offset: LongInt; Mode: TSeekMode):
454 LongInt; cdecl;
455 begin
456 Result := PMemoryIORec(Handle).Position;
457 case Mode of
458 smFromBeginning: Result := Offset;
459 smFromCurrent: Result := PMemoryIORec(Handle).Position + Offset;
460 smFromEnd: Result := PMemoryIORec(Handle).Size + Offset;
461 end;
462 //Result := ClampInt(Result, 0, PMemoryIORec(Handle).Size); don't do this - some file formats use it
463 PMemoryIORec(Handle).Position := Result;
464 end;
466 function MemoryTell(Handle: TImagingHandle): LongInt; cdecl;
467 begin
468 Result := PMemoryIORec(Handle).Position;
469 end;
471 function MemoryRead(Handle: TImagingHandle; Buffer: Pointer; Count: LongInt):
472 LongInt; cdecl;
473 var
474 Rec: PMemoryIORec;
475 begin
476 Rec := PMemoryIORec(Handle);
477 Result := Count;
478 if Rec.Position + Count > Rec.Size then
479 Result := Rec.Size - Rec.Position;
480 Move(Rec.Data[Rec.Position], Buffer^, Result);
481 Rec.Position := Rec.Position + Result;
482 end;
484 function MemoryWrite(Handle: TImagingHandle; Buffer: Pointer; Count: LongInt):
485 LongInt; cdecl;
486 var
487 Rec: PMemoryIORec;
488 begin
489 Rec := PMemoryIORec(Handle);
490 Result := Count;
491 if Rec.Position + Count > Rec.Size then
492 Result := Rec.Size - Rec.Position;
493 Move(Buffer^, Rec.Data[Rec.Position], Result);
494 Rec.Position := Rec.Position + Result;
495 end;
497 { Helper IO functions }
499 function GetInputSize(IOFunctions: TIOFunctions; Handle: TImagingHandle): LongInt;
500 var
501 OldPos: Int64;
502 begin
503 OldPos := IOFunctions.Tell(Handle);
504 IOFunctions.Seek(Handle, 0, smFromEnd);
505 Result := IOFunctions.Tell(Handle);
506 IOFunctions.Seek(Handle, OldPos, smFromBeginning);
507 end;
509 function PrepareMemIO(Data: Pointer; Size: LongInt): TMemoryIORec;
510 begin
511 Result.Data := Data;
512 Result.Position := 0;
513 Result.Size := Size;
514 end;
516 initialization
517 OriginalFileIO.OpenRead := FileOpenRead;
518 OriginalFileIO.OpenWrite := FileOpenWrite;
519 OriginalFileIO.Close := FileClose;
520 OriginalFileIO.Eof := FileEof;
521 OriginalFileIO.Seek := FileSeek;
522 OriginalFileIO.Tell := FileTell;
523 OriginalFileIO.Read := FileRead;
524 OriginalFileIO.Write := FileWrite;
526 StreamIO.OpenRead := StreamOpenRead;
527 StreamIO.OpenWrite := StreamOpenWrite;
528 StreamIO.Close := StreamClose;
529 StreamIO.Eof := StreamEof;
530 StreamIO.Seek := StreamSeek;
531 StreamIO.Tell := StreamTell;
532 StreamIO.Read := StreamRead;
533 StreamIO.Write := StreamWrite;
535 MemoryIO.OpenRead := MemoryOpenRead;
536 MemoryIO.OpenWrite := MemoryOpenWrite;
537 MemoryIO.Close := MemoryClose;
538 MemoryIO.Eof := MemoryEof;
539 MemoryIO.Seek := MemorySeek;
540 MemoryIO.Tell := MemoryTell;
541 MemoryIO.Read := MemoryRead;
542 MemoryIO.Write := MemoryWrite;
544 ResetFileIO;
547 File Notes:
549 -- TODOS ----------------------------------------------------
550 - nothing now
552 -- 0.23 Changes/Bug Fixes -----------------------------------
553 - Added merge between buffered read-only and write-only file
554 stream adapters - TIFF saving needed both reading and writing.
555 - Fixed bug causing wrong value of TBufferedWriteFile.Size
556 (needed to add buffer pos to size).
558 -- 0.21 Changes/Bug Fixes -----------------------------------
559 - Removed TMemoryIORec.Written, use Position to get proper memory
560 position (Written didn't take Seeks into account).
561 - Added TBufferedReadFile and TBufferedWriteFile classes for
562 buffered file reading/writting. File IO functions now use these
563 classes resulting in performance increase mainly in file formats
564 that read/write many small chunks.
565 - Added fmShareDenyWrite to FileOpenRead. You can now read
566 files opened for reading by Imaging from other apps.
567 - Added GetInputSize and PrepareMemIO helper functions.
569 -- 0.19 Changes/Bug Fixes -----------------------------------
570 - changed behaviour of MemorySeek to act as TStream
571 based Seeks
573 end.