cannam@89: {*******************************************************} cannam@89: { } cannam@89: { Borland Delphi Supplemental Components } cannam@89: { ZLIB Data Compression Interface Unit } cannam@89: { } cannam@89: { Copyright (c) 1997,99 Borland Corporation } cannam@89: { } cannam@89: {*******************************************************} cannam@89: cannam@89: { Updated for zlib 1.2.x by Cosmin Truta } cannam@89: cannam@89: unit ZLib; cannam@89: cannam@89: interface cannam@89: cannam@89: uses SysUtils, Classes; cannam@89: cannam@89: type cannam@89: TAlloc = function (AppData: Pointer; Items, Size: Integer): Pointer; cdecl; cannam@89: TFree = procedure (AppData, Block: Pointer); cdecl; cannam@89: cannam@89: // Internal structure. Ignore. cannam@89: TZStreamRec = packed record cannam@89: next_in: PChar; // next input byte cannam@89: avail_in: Integer; // number of bytes available at next_in cannam@89: total_in: Longint; // total nb of input bytes read so far cannam@89: cannam@89: next_out: PChar; // next output byte should be put here cannam@89: avail_out: Integer; // remaining free space at next_out cannam@89: total_out: Longint; // total nb of bytes output so far cannam@89: cannam@89: msg: PChar; // last error message, NULL if no error cannam@89: internal: Pointer; // not visible by applications cannam@89: cannam@89: zalloc: TAlloc; // used to allocate the internal state cannam@89: zfree: TFree; // used to free the internal state cannam@89: AppData: Pointer; // private data object passed to zalloc and zfree cannam@89: cannam@89: data_type: Integer; // best guess about the data type: ascii or binary cannam@89: adler: Longint; // adler32 value of the uncompressed data cannam@89: reserved: Longint; // reserved for future use cannam@89: end; cannam@89: cannam@89: // Abstract ancestor class cannam@89: TCustomZlibStream = class(TStream) cannam@89: private cannam@89: FStrm: TStream; cannam@89: FStrmPos: Integer; cannam@89: FOnProgress: TNotifyEvent; cannam@89: FZRec: TZStreamRec; cannam@89: FBuffer: array [Word] of Char; cannam@89: protected cannam@89: procedure Progress(Sender: TObject); dynamic; cannam@89: property OnProgress: TNotifyEvent read FOnProgress write FOnProgress; cannam@89: constructor Create(Strm: TStream); cannam@89: end; cannam@89: cannam@89: { TCompressionStream compresses data on the fly as data is written to it, and cannam@89: stores the compressed data to another stream. cannam@89: cannam@89: TCompressionStream is write-only and strictly sequential. Reading from the cannam@89: stream will raise an exception. Using Seek to move the stream pointer cannam@89: will raise an exception. cannam@89: cannam@89: Output data is cached internally, written to the output stream only when cannam@89: the internal output buffer is full. All pending output data is flushed cannam@89: when the stream is destroyed. cannam@89: cannam@89: The Position property returns the number of uncompressed bytes of cannam@89: data that have been written to the stream so far. cannam@89: cannam@89: CompressionRate returns the on-the-fly percentage by which the original cannam@89: data has been compressed: (1 - (CompressedBytes / UncompressedBytes)) * 100 cannam@89: If raw data size = 100 and compressed data size = 25, the CompressionRate cannam@89: is 75% cannam@89: cannam@89: The OnProgress event is called each time the output buffer is filled and cannam@89: written to the output stream. This is useful for updating a progress cannam@89: indicator when you are writing a large chunk of data to the compression cannam@89: stream in a single call.} cannam@89: cannam@89: cannam@89: TCompressionLevel = (clNone, clFastest, clDefault, clMax); cannam@89: cannam@89: TCompressionStream = class(TCustomZlibStream) cannam@89: private cannam@89: function GetCompressionRate: Single; cannam@89: public cannam@89: constructor Create(CompressionLevel: TCompressionLevel; Dest: TStream); cannam@89: destructor Destroy; override; cannam@89: function Read(var Buffer; Count: Longint): Longint; override; cannam@89: function Write(const Buffer; Count: Longint): Longint; override; cannam@89: function Seek(Offset: Longint; Origin: Word): Longint; override; cannam@89: property CompressionRate: Single read GetCompressionRate; cannam@89: property OnProgress; cannam@89: end; cannam@89: cannam@89: { TDecompressionStream decompresses data on the fly as data is read from it. cannam@89: cannam@89: Compressed data comes from a separate source stream. TDecompressionStream cannam@89: is read-only and unidirectional; you can seek forward in the stream, but not cannam@89: backwards. The special case of setting the stream position to zero is cannam@89: allowed. Seeking forward decompresses data until the requested position in cannam@89: the uncompressed data has been reached. Seeking backwards, seeking relative cannam@89: to the end of the stream, requesting the size of the stream, and writing to cannam@89: the stream will raise an exception. cannam@89: cannam@89: The Position property returns the number of bytes of uncompressed data that cannam@89: have been read from the stream so far. cannam@89: cannam@89: The OnProgress event is called each time the internal input buffer of cannam@89: compressed data is exhausted and the next block is read from the input stream. cannam@89: This is useful for updating a progress indicator when you are reading a cannam@89: large chunk of data from the decompression stream in a single call.} cannam@89: cannam@89: TDecompressionStream = class(TCustomZlibStream) cannam@89: public cannam@89: constructor Create(Source: TStream); cannam@89: destructor Destroy; override; cannam@89: function Read(var Buffer; Count: Longint): Longint; override; cannam@89: function Write(const Buffer; Count: Longint): Longint; override; cannam@89: function Seek(Offset: Longint; Origin: Word): Longint; override; cannam@89: property OnProgress; cannam@89: end; cannam@89: cannam@89: cannam@89: cannam@89: { CompressBuf compresses data, buffer to buffer, in one call. cannam@89: In: InBuf = ptr to compressed data cannam@89: InBytes = number of bytes in InBuf cannam@89: Out: OutBuf = ptr to newly allocated buffer containing decompressed data cannam@89: OutBytes = number of bytes in OutBuf } cannam@89: procedure CompressBuf(const InBuf: Pointer; InBytes: Integer; cannam@89: out OutBuf: Pointer; out OutBytes: Integer); cannam@89: cannam@89: cannam@89: { DecompressBuf decompresses data, buffer to buffer, in one call. cannam@89: In: InBuf = ptr to compressed data cannam@89: InBytes = number of bytes in InBuf cannam@89: OutEstimate = zero, or est. size of the decompressed data cannam@89: Out: OutBuf = ptr to newly allocated buffer containing decompressed data cannam@89: OutBytes = number of bytes in OutBuf } cannam@89: procedure DecompressBuf(const InBuf: Pointer; InBytes: Integer; cannam@89: OutEstimate: Integer; out OutBuf: Pointer; out OutBytes: Integer); cannam@89: cannam@89: { DecompressToUserBuf decompresses data, buffer to buffer, in one call. cannam@89: In: InBuf = ptr to compressed data cannam@89: InBytes = number of bytes in InBuf cannam@89: Out: OutBuf = ptr to user-allocated buffer to contain decompressed data cannam@89: BufSize = number of bytes in OutBuf } cannam@89: procedure DecompressToUserBuf(const InBuf: Pointer; InBytes: Integer; cannam@89: const OutBuf: Pointer; BufSize: Integer); cannam@89: cannam@89: const cannam@89: zlib_version = '1.2.7'; cannam@89: cannam@89: type cannam@89: EZlibError = class(Exception); cannam@89: ECompressionError = class(EZlibError); cannam@89: EDecompressionError = class(EZlibError); cannam@89: cannam@89: implementation cannam@89: cannam@89: uses ZLibConst; cannam@89: cannam@89: const cannam@89: Z_NO_FLUSH = 0; cannam@89: Z_PARTIAL_FLUSH = 1; cannam@89: Z_SYNC_FLUSH = 2; cannam@89: Z_FULL_FLUSH = 3; cannam@89: Z_FINISH = 4; cannam@89: cannam@89: Z_OK = 0; cannam@89: Z_STREAM_END = 1; cannam@89: Z_NEED_DICT = 2; cannam@89: Z_ERRNO = (-1); cannam@89: Z_STREAM_ERROR = (-2); cannam@89: Z_DATA_ERROR = (-3); cannam@89: Z_MEM_ERROR = (-4); cannam@89: Z_BUF_ERROR = (-5); cannam@89: Z_VERSION_ERROR = (-6); cannam@89: cannam@89: Z_NO_COMPRESSION = 0; cannam@89: Z_BEST_SPEED = 1; cannam@89: Z_BEST_COMPRESSION = 9; cannam@89: Z_DEFAULT_COMPRESSION = (-1); cannam@89: cannam@89: Z_FILTERED = 1; cannam@89: Z_HUFFMAN_ONLY = 2; cannam@89: Z_RLE = 3; cannam@89: Z_DEFAULT_STRATEGY = 0; cannam@89: cannam@89: Z_BINARY = 0; cannam@89: Z_ASCII = 1; cannam@89: Z_UNKNOWN = 2; cannam@89: cannam@89: Z_DEFLATED = 8; cannam@89: cannam@89: cannam@89: {$L adler32.obj} cannam@89: {$L compress.obj} cannam@89: {$L crc32.obj} cannam@89: {$L deflate.obj} cannam@89: {$L infback.obj} cannam@89: {$L inffast.obj} cannam@89: {$L inflate.obj} cannam@89: {$L inftrees.obj} cannam@89: {$L trees.obj} cannam@89: {$L uncompr.obj} cannam@89: {$L zutil.obj} cannam@89: cannam@89: procedure adler32; external; cannam@89: procedure compressBound; external; cannam@89: procedure crc32; external; cannam@89: procedure deflateInit2_; external; cannam@89: procedure deflateParams; external; cannam@89: cannam@89: function _malloc(Size: Integer): Pointer; cdecl; cannam@89: begin cannam@89: Result := AllocMem(Size); cannam@89: end; cannam@89: cannam@89: procedure _free(Block: Pointer); cdecl; cannam@89: begin cannam@89: FreeMem(Block); cannam@89: end; cannam@89: cannam@89: procedure _memset(P: Pointer; B: Byte; count: Integer); cdecl; cannam@89: begin cannam@89: FillChar(P^, count, B); cannam@89: end; cannam@89: cannam@89: procedure _memcpy(dest, source: Pointer; count: Integer); cdecl; cannam@89: begin cannam@89: Move(source^, dest^, count); cannam@89: end; cannam@89: cannam@89: cannam@89: cannam@89: // deflate compresses data cannam@89: function deflateInit_(var strm: TZStreamRec; level: Integer; version: PChar; cannam@89: recsize: Integer): Integer; external; cannam@89: function deflate(var strm: TZStreamRec; flush: Integer): Integer; external; cannam@89: function deflateEnd(var strm: TZStreamRec): Integer; external; cannam@89: cannam@89: // inflate decompresses data cannam@89: function inflateInit_(var strm: TZStreamRec; version: PChar; cannam@89: recsize: Integer): Integer; external; cannam@89: function inflate(var strm: TZStreamRec; flush: Integer): Integer; external; cannam@89: function inflateEnd(var strm: TZStreamRec): Integer; external; cannam@89: function inflateReset(var strm: TZStreamRec): Integer; external; cannam@89: cannam@89: cannam@89: function zlibAllocMem(AppData: Pointer; Items, Size: Integer): Pointer; cdecl; cannam@89: begin cannam@89: // GetMem(Result, Items*Size); cannam@89: Result := AllocMem(Items * Size); cannam@89: end; cannam@89: cannam@89: procedure zlibFreeMem(AppData, Block: Pointer); cdecl; cannam@89: begin cannam@89: FreeMem(Block); cannam@89: end; cannam@89: cannam@89: {function zlibCheck(code: Integer): Integer; cannam@89: begin cannam@89: Result := code; cannam@89: if code < 0 then cannam@89: raise EZlibError.Create('error'); //!! cannam@89: end;} cannam@89: cannam@89: function CCheck(code: Integer): Integer; cannam@89: begin cannam@89: Result := code; cannam@89: if code < 0 then cannam@89: raise ECompressionError.Create('error'); //!! cannam@89: end; cannam@89: cannam@89: function DCheck(code: Integer): Integer; cannam@89: begin cannam@89: Result := code; cannam@89: if code < 0 then cannam@89: raise EDecompressionError.Create('error'); //!! cannam@89: end; cannam@89: cannam@89: procedure CompressBuf(const InBuf: Pointer; InBytes: Integer; cannam@89: out OutBuf: Pointer; out OutBytes: Integer); cannam@89: var cannam@89: strm: TZStreamRec; cannam@89: P: Pointer; cannam@89: begin cannam@89: FillChar(strm, sizeof(strm), 0); cannam@89: strm.zalloc := zlibAllocMem; cannam@89: strm.zfree := zlibFreeMem; cannam@89: OutBytes := ((InBytes + (InBytes div 10) + 12) + 255) and not 255; cannam@89: GetMem(OutBuf, OutBytes); cannam@89: try cannam@89: strm.next_in := InBuf; cannam@89: strm.avail_in := InBytes; cannam@89: strm.next_out := OutBuf; cannam@89: strm.avail_out := OutBytes; cannam@89: CCheck(deflateInit_(strm, Z_BEST_COMPRESSION, zlib_version, sizeof(strm))); cannam@89: try cannam@89: while CCheck(deflate(strm, Z_FINISH)) <> Z_STREAM_END do cannam@89: begin cannam@89: P := OutBuf; cannam@89: Inc(OutBytes, 256); cannam@89: ReallocMem(OutBuf, OutBytes); cannam@89: strm.next_out := PChar(Integer(OutBuf) + (Integer(strm.next_out) - Integer(P))); cannam@89: strm.avail_out := 256; cannam@89: end; cannam@89: finally cannam@89: CCheck(deflateEnd(strm)); cannam@89: end; cannam@89: ReallocMem(OutBuf, strm.total_out); cannam@89: OutBytes := strm.total_out; cannam@89: except cannam@89: FreeMem(OutBuf); cannam@89: raise cannam@89: end; cannam@89: end; cannam@89: cannam@89: cannam@89: procedure DecompressBuf(const InBuf: Pointer; InBytes: Integer; cannam@89: OutEstimate: Integer; out OutBuf: Pointer; out OutBytes: Integer); cannam@89: var cannam@89: strm: TZStreamRec; cannam@89: P: Pointer; cannam@89: BufInc: Integer; cannam@89: begin cannam@89: FillChar(strm, sizeof(strm), 0); cannam@89: strm.zalloc := zlibAllocMem; cannam@89: strm.zfree := zlibFreeMem; cannam@89: BufInc := (InBytes + 255) and not 255; cannam@89: if OutEstimate = 0 then cannam@89: OutBytes := BufInc cannam@89: else cannam@89: OutBytes := OutEstimate; cannam@89: GetMem(OutBuf, OutBytes); cannam@89: try cannam@89: strm.next_in := InBuf; cannam@89: strm.avail_in := InBytes; cannam@89: strm.next_out := OutBuf; cannam@89: strm.avail_out := OutBytes; cannam@89: DCheck(inflateInit_(strm, zlib_version, sizeof(strm))); cannam@89: try cannam@89: while DCheck(inflate(strm, Z_NO_FLUSH)) <> Z_STREAM_END do cannam@89: begin cannam@89: P := OutBuf; cannam@89: Inc(OutBytes, BufInc); cannam@89: ReallocMem(OutBuf, OutBytes); cannam@89: strm.next_out := PChar(Integer(OutBuf) + (Integer(strm.next_out) - Integer(P))); cannam@89: strm.avail_out := BufInc; cannam@89: end; cannam@89: finally cannam@89: DCheck(inflateEnd(strm)); cannam@89: end; cannam@89: ReallocMem(OutBuf, strm.total_out); cannam@89: OutBytes := strm.total_out; cannam@89: except cannam@89: FreeMem(OutBuf); cannam@89: raise cannam@89: end; cannam@89: end; cannam@89: cannam@89: procedure DecompressToUserBuf(const InBuf: Pointer; InBytes: Integer; cannam@89: const OutBuf: Pointer; BufSize: Integer); cannam@89: var cannam@89: strm: TZStreamRec; cannam@89: begin cannam@89: FillChar(strm, sizeof(strm), 0); cannam@89: strm.zalloc := zlibAllocMem; cannam@89: strm.zfree := zlibFreeMem; cannam@89: strm.next_in := InBuf; cannam@89: strm.avail_in := InBytes; cannam@89: strm.next_out := OutBuf; cannam@89: strm.avail_out := BufSize; cannam@89: DCheck(inflateInit_(strm, zlib_version, sizeof(strm))); cannam@89: try cannam@89: if DCheck(inflate(strm, Z_FINISH)) <> Z_STREAM_END then cannam@89: raise EZlibError.CreateRes(@sTargetBufferTooSmall); cannam@89: finally cannam@89: DCheck(inflateEnd(strm)); cannam@89: end; cannam@89: end; cannam@89: cannam@89: // TCustomZlibStream cannam@89: cannam@89: constructor TCustomZLibStream.Create(Strm: TStream); cannam@89: begin cannam@89: inherited Create; cannam@89: FStrm := Strm; cannam@89: FStrmPos := Strm.Position; cannam@89: FZRec.zalloc := zlibAllocMem; cannam@89: FZRec.zfree := zlibFreeMem; cannam@89: end; cannam@89: cannam@89: procedure TCustomZLibStream.Progress(Sender: TObject); cannam@89: begin cannam@89: if Assigned(FOnProgress) then FOnProgress(Sender); cannam@89: end; cannam@89: cannam@89: cannam@89: // TCompressionStream cannam@89: cannam@89: constructor TCompressionStream.Create(CompressionLevel: TCompressionLevel; cannam@89: Dest: TStream); cannam@89: const cannam@89: Levels: array [TCompressionLevel] of ShortInt = cannam@89: (Z_NO_COMPRESSION, Z_BEST_SPEED, Z_DEFAULT_COMPRESSION, Z_BEST_COMPRESSION); cannam@89: begin cannam@89: inherited Create(Dest); cannam@89: FZRec.next_out := FBuffer; cannam@89: FZRec.avail_out := sizeof(FBuffer); cannam@89: CCheck(deflateInit_(FZRec, Levels[CompressionLevel], zlib_version, sizeof(FZRec))); cannam@89: end; cannam@89: cannam@89: destructor TCompressionStream.Destroy; cannam@89: begin cannam@89: FZRec.next_in := nil; cannam@89: FZRec.avail_in := 0; cannam@89: try cannam@89: if FStrm.Position <> FStrmPos then FStrm.Position := FStrmPos; cannam@89: while (CCheck(deflate(FZRec, Z_FINISH)) <> Z_STREAM_END) cannam@89: and (FZRec.avail_out = 0) do cannam@89: begin cannam@89: FStrm.WriteBuffer(FBuffer, sizeof(FBuffer)); cannam@89: FZRec.next_out := FBuffer; cannam@89: FZRec.avail_out := sizeof(FBuffer); cannam@89: end; cannam@89: if FZRec.avail_out < sizeof(FBuffer) then cannam@89: FStrm.WriteBuffer(FBuffer, sizeof(FBuffer) - FZRec.avail_out); cannam@89: finally cannam@89: deflateEnd(FZRec); cannam@89: end; cannam@89: inherited Destroy; cannam@89: end; cannam@89: cannam@89: function TCompressionStream.Read(var Buffer; Count: Longint): Longint; cannam@89: begin cannam@89: raise ECompressionError.CreateRes(@sInvalidStreamOp); cannam@89: end; cannam@89: cannam@89: function TCompressionStream.Write(const Buffer; Count: Longint): Longint; cannam@89: begin cannam@89: FZRec.next_in := @Buffer; cannam@89: FZRec.avail_in := Count; cannam@89: if FStrm.Position <> FStrmPos then FStrm.Position := FStrmPos; cannam@89: while (FZRec.avail_in > 0) do cannam@89: begin cannam@89: CCheck(deflate(FZRec, 0)); cannam@89: if FZRec.avail_out = 0 then cannam@89: begin cannam@89: FStrm.WriteBuffer(FBuffer, sizeof(FBuffer)); cannam@89: FZRec.next_out := FBuffer; cannam@89: FZRec.avail_out := sizeof(FBuffer); cannam@89: FStrmPos := FStrm.Position; cannam@89: Progress(Self); cannam@89: end; cannam@89: end; cannam@89: Result := Count; cannam@89: end; cannam@89: cannam@89: function TCompressionStream.Seek(Offset: Longint; Origin: Word): Longint; cannam@89: begin cannam@89: if (Offset = 0) and (Origin = soFromCurrent) then cannam@89: Result := FZRec.total_in cannam@89: else cannam@89: raise ECompressionError.CreateRes(@sInvalidStreamOp); cannam@89: end; cannam@89: cannam@89: function TCompressionStream.GetCompressionRate: Single; cannam@89: begin cannam@89: if FZRec.total_in = 0 then cannam@89: Result := 0 cannam@89: else cannam@89: Result := (1.0 - (FZRec.total_out / FZRec.total_in)) * 100.0; cannam@89: end; cannam@89: cannam@89: cannam@89: // TDecompressionStream cannam@89: cannam@89: constructor TDecompressionStream.Create(Source: TStream); cannam@89: begin cannam@89: inherited Create(Source); cannam@89: FZRec.next_in := FBuffer; cannam@89: FZRec.avail_in := 0; cannam@89: DCheck(inflateInit_(FZRec, zlib_version, sizeof(FZRec))); cannam@89: end; cannam@89: cannam@89: destructor TDecompressionStream.Destroy; cannam@89: begin cannam@89: FStrm.Seek(-FZRec.avail_in, 1); cannam@89: inflateEnd(FZRec); cannam@89: inherited Destroy; cannam@89: end; cannam@89: cannam@89: function TDecompressionStream.Read(var Buffer; Count: Longint): Longint; cannam@89: begin cannam@89: FZRec.next_out := @Buffer; cannam@89: FZRec.avail_out := Count; cannam@89: if FStrm.Position <> FStrmPos then FStrm.Position := FStrmPos; cannam@89: while (FZRec.avail_out > 0) do cannam@89: begin cannam@89: if FZRec.avail_in = 0 then cannam@89: begin cannam@89: FZRec.avail_in := FStrm.Read(FBuffer, sizeof(FBuffer)); cannam@89: if FZRec.avail_in = 0 then cannam@89: begin cannam@89: Result := Count - FZRec.avail_out; cannam@89: Exit; cannam@89: end; cannam@89: FZRec.next_in := FBuffer; cannam@89: FStrmPos := FStrm.Position; cannam@89: Progress(Self); cannam@89: end; cannam@89: CCheck(inflate(FZRec, 0)); cannam@89: end; cannam@89: Result := Count; cannam@89: end; cannam@89: cannam@89: function TDecompressionStream.Write(const Buffer; Count: Longint): Longint; cannam@89: begin cannam@89: raise EDecompressionError.CreateRes(@sInvalidStreamOp); cannam@89: end; cannam@89: cannam@89: function TDecompressionStream.Seek(Offset: Longint; Origin: Word): Longint; cannam@89: var cannam@89: I: Integer; cannam@89: Buf: array [0..4095] of Char; cannam@89: begin cannam@89: if (Offset = 0) and (Origin = soFromBeginning) then cannam@89: begin cannam@89: DCheck(inflateReset(FZRec)); cannam@89: FZRec.next_in := FBuffer; cannam@89: FZRec.avail_in := 0; cannam@89: FStrm.Position := 0; cannam@89: FStrmPos := 0; cannam@89: end cannam@89: else if ( (Offset >= 0) and (Origin = soFromCurrent)) or cannam@89: ( ((Offset - FZRec.total_out) > 0) and (Origin = soFromBeginning)) then cannam@89: begin cannam@89: if Origin = soFromBeginning then Dec(Offset, FZRec.total_out); cannam@89: if Offset > 0 then cannam@89: begin cannam@89: for I := 1 to Offset div sizeof(Buf) do cannam@89: ReadBuffer(Buf, sizeof(Buf)); cannam@89: ReadBuffer(Buf, Offset mod sizeof(Buf)); cannam@89: end; cannam@89: end cannam@89: else cannam@89: raise EDecompressionError.CreateRes(@sInvalidStreamOp); cannam@89: Result := FZRec.total_out; cannam@89: end; cannam@89: cannam@89: cannam@89: end.