From 78860b6c039c5077a420c17d91fb3ead99d75804 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Sat, 31 Aug 2024 17:06:29 -0700 Subject: [PATCH] Add LZC compression, part 2 (of 2) Implemented compression. Added to NuFX (LZC12 and LZC16), to the compression regression tests, and to the bulk compress test. Added notes to the format doc index. The compressor supports everything compress 4.0 did, including the (deprecated) non-block behavior. --- DiskArc/Arc/NuFX_FileEntry.cs | 12 +- DiskArc/Comp/LZC-notes.md | 23 +- DiskArc/Comp/LZCStream.cs | 303 +++++++++++++++++++++++++-- DiskArcTests/TestCompression.cs | 69 ++++-- cp2_wpf/LibTest/BulkCompress.xaml | 11 +- cp2_wpf/LibTest/BulkCompress.xaml.cs | 11 +- cp2_wpf/LibTest/BulkCompressTest.cs | 16 +- docs/doc-index.html | 2 + ndocs/doc-index.html | 2 + 9 files changed, 387 insertions(+), 62 deletions(-) diff --git a/DiskArc/Arc/NuFX_FileEntry.cs b/DiskArc/Arc/NuFX_FileEntry.cs index 78f293c..85d2641 100644 --- a/DiskArc/Arc/NuFX_FileEntry.cs +++ b/DiskArc/Arc/NuFX_FileEntry.cs @@ -1290,7 +1290,7 @@ internal ArcReadStream CreateReadStream(FilePart part) { case CompressionFormat.LZC12: case CompressionFormat.LZC16: expander = new LZCStream(Archive.DataStream, CompressionMode.Decompress, - true, thread.mCompThreadEof, 16); + true, thread.mCompThreadEof, 16, true); break; default: throw new NotSupportedException("Compression format not supported"); @@ -1329,6 +1329,8 @@ internal void AddPart(FilePart part, IPartSource partSource, case CompressionFormat.Squeeze: case CompressionFormat.NuLZW1: case CompressionFormat.NuLZW2: + case CompressionFormat.LZC12: + case CompressionFormat.LZC16: break; default: throw new ArgumentException("Compression format " + compressFmt + @@ -1646,6 +1648,14 @@ private ThreadHeader WritePart(Stream outputStream, EditPart editPart) { compStream = new NuLZWStream(outputStream, CompressionMode.Compress, true, true, -1); break; + case CompressionFormat.LZC12: + compStream = new LZCStream(outputStream, CompressionMode.Compress, true, + -1, 12, true); + break; + case CompressionFormat.LZC16: + compStream = new LZCStream(outputStream, CompressionMode.Compress, true, + -1, 16, true); + break; default: throw new ArgumentException("Invalid compression format " + format + ": " + FileName); diff --git a/DiskArc/Comp/LZC-notes.md b/DiskArc/Comp/LZC-notes.md index f584bf9..29dc4a1 100644 --- a/DiskArc/Comp/LZC-notes.md +++ b/DiskArc/Comp/LZC-notes.md @@ -6,12 +6,12 @@ ## General ## -For several years, the [`compress` command](https://en.wikipedia.org/wiki/Compress_(software)) -was the primary way to compress files on UNIX systems. It used an algorithm based on -Lempel-Ziv-Welch (LZW) [encoding](https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch), -which was faster and had better compression ratios than previous programs like `pack` and -`compact`, which used RLE and Huffman encoding. The specific implementation of the algorithm -is sometimes referred to as `LZC`. +First released in 1984, the `compress` [command](https://en.wikipedia.org/wiki/Compress_(software)) +was the preferred way to compress individual files on large systems for several years. It uses an +algorithm based on Lempel-Ziv-Welch (LZW) +[encoding](https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch), which is faster and +has better compression ratios than previous programs like `pack` and `compact`, which used RLE and +Huffman encoding. The specific implementation of the algorithm is sometimes referred to as `LZC`. `compress` marked its files by adding `.Z` to the filename. It was largely supplanted by `gzip`, which has better compression ratios and wasn't subject to Unisys patents. @@ -20,12 +20,12 @@ The program went through a few iterations, with primary development ending in 19 the release of version 4.0. Various minor versions were released by different authors, generally to improve compatibility with specific systems, or to tweak the way clear codes were issued. -The maximum width of the LZW codes, which affects how much memory is required, could be -configured at compile time and overridden to be lower at run time. The value could be set +The maximum width of the LZW codes, which affects how much memory is required by the program, could +be configured at compile time and overridden to be lower at run time. The value could be set between 9 and 16, inclusive. This impacted decompression, meaning that an implementation limited to 12-bit codes could not decompress a file that used 16-bit codes. -GS/ShrinkIt can decompress NuFX threads compressed with LZC, up to 16 bits. It does not support +GS/ShrinkIt can decompress NuFX threads compressed with LZC, up to 16 bits. It does not perform compression in that format, but it is possible to create such archives with NuLib. ## Detail ## @@ -45,8 +45,7 @@ decompression side also reads the input in 8-code chunks. When operating in "bl transition to a new code with happens to occur at a multiple of 8 codes, so there are no alignment gaps in the output unless a block clear code is emitted. With the older (v2) behavior, the clear code is not reserved, which increases the number of available 9-bit codes by 1, so a gap -will appear at the first code width change. This behavior, and the somewhat convoluted -implementation in `compress` v4.0, has led to [bugs](https://github.com/vapier/ncompress/issues/5) -in some implementations. +will appear at the first code width change. This somewhat obscure behavior has led to +[bugs](https://github.com/vapier/ncompress/issues/5) in some implementations. The only time a partial chunk is written is at the end of the file. diff --git a/DiskArc/Comp/LZCStream.cs b/DiskArc/Comp/LZCStream.cs index eb0054b..fe5b53b 100644 --- a/DiskArc/Comp/LZCStream.cs +++ b/DiskArc/Comp/LZCStream.cs @@ -16,7 +16,6 @@ using System; using System.Diagnostics; using System.IO.Compression; -using System.Text; using CommonUtil; @@ -26,10 +25,12 @@ namespace DiskArc.Comp { /// UNIX "compress" command, v4.0. /// /// - /// Compatibility is more important than performance or readability, so this follows the + /// Compatibility is more important than performance or readability, so this follows the /// original source code fairly closely, retaining naming conventions and code structure. /// I have discarded the various memory-saving techniques, like hard-limiting the maximum - /// number of bits to less than 16. + /// number of bits to less than 16. + /// To function as a streaming class, rather than a file filter, the structure of the + /// main loops had to be altered significantly. /// public class LZCStream : Stream { // Stream characteristics. @@ -81,8 +82,9 @@ public override long Position { private const int BIT_MASK = 0x1f; private const int BLOCK_MASK = 0x80; - private int mComp_BitLimit; // caller-requested bit limit - private long mExpInputRemaining; + private int mCompBitLimit; // caller-requested bit limit + private bool mComp_block_compress; // if true, issue CLEAR codes after table fills + private long mExpInputRemaining; // compressed data length, when expanding /// @@ -97,8 +99,9 @@ public override long Position { /// Decompression: length of compressed data. /// Compression: maximum code width, must be in the range /// [9,16]. + /// Compression: use block mode (recommended)? public LZCStream(Stream compDataStream, CompressionMode mode, bool leaveOpen, - long compDataStreamLen, int maxBits) { + long compDataStreamLen, int maxBits, bool blockMode) { if (maxBits < INIT_BITS || maxBits > BITS) { throw new ArgumentOutOfRangeException(nameof(maxBits), maxBits, "must be [9,16]"); } @@ -106,13 +109,17 @@ public LZCStream(Stream compDataStream, CompressionMode mode, bool leaveOpen, mCompressionMode = mode; mLeaveOpen = leaveOpen; mExpInputRemaining = compDataStreamLen; - mComp_BitLimit = maxBits; + mCompBitLimit = maxBits; + mComp_block_compress = blockMode; if (mode == CompressionMode.Compress) { Debug.Assert(compDataStream.CanWrite); InitCompress(); } else if (mode == CompressionMode.Decompress) { Debug.Assert(compDataStream.CanRead); + if (compDataStreamLen < 0) { + throw new ArgumentException("Invalid value for " + nameof(compDataStreamLen)); + } InitExpand(); } else { throw new ArgumentException("Invalid mode: " + mode); @@ -177,26 +184,278 @@ public override void SetLength(long value) { #region Compress + private int mComp_offset; + private int mComp_in_count; // length of input (rolls over after 2GB) + private int mComp_bytes_out; // length of compressed output + private int mComp_out_count; // # of codes output (for debugging) + + private bool mComp_clear_flg; + private int mComp_ratio; + private const int CHECK_GAP = 10000; // ratio check interval + private int mComp_checkpoint; + + private int mComp_n_bits; // number of bits/code + private int mComp_maxbits; // user settable max # bits/code + private int mComp_maxcode; // maximum code, given n_bits + private int mComp_maxmaxcode; + private int mComp_free_ent; // first unused entry + private int mComp_ent; + // Tables used for compression. private int[]? mComp_htab; private ushort[]? mComp_codetab; - int mComp_hsize = HSIZE; // for dynamic table sizing [not used] + private byte[]? mComp_buf; + + private int mComp_hsize; // for dynamic table sizing + private int mComp_hshift; + + private bool mCompHeaderWritten; + private bool mFirstByteRead; /// /// Prepares object for compression. /// private void InitCompress() { + mComp_buf = new byte[BITS]; mComp_htab = new int[HSIZE]; mComp_codetab = new ushort[HSIZE]; - // TODO + mComp_hsize = HSIZE; + + mComp_maxbits = mCompBitLimit; + mComp_maxmaxcode = 1 << mCompBitLimit; + + int hshift = 0; + for (int fcode = mComp_hsize; fcode < 65536; fcode *= 2) { + hshift++; + } + mComp_hshift = 8 - hshift; // set hash code range bound + ClearHash(mComp_hsize); // clear hash table + + mComp_ratio = 0; + + mComp_offset = 0; + mComp_out_count = 0; + mComp_clear_flg = false; + mComp_ratio = 0; + mComp_in_count = 0; + mComp_checkpoint = CHECK_GAP; + mComp_n_bits = INIT_BITS; + mComp_maxcode = MAXCODE(mComp_n_bits); + mComp_free_ent = mComp_block_compress ? FIRST : 256; + + mCompHeaderWritten = false; + mFirstByteRead = false; } /// /// Receives uncompressed data to be compressed. (Stream call.) /// - public override void Write(byte[] data, int offset, int length) { - // TODO + public override void Write(byte[] buffer, int offset, int count) { + Debug.Assert(mComp_htab != null && mComp_codetab != null); + + if (!mCompHeaderWritten) { + WriteHeader(); + mCompHeaderWritten = true; + } + + if (count == 0) { + return; // nothing to do + } + + if (!mFirstByteRead) { + // First byte is handled specially. + mComp_ent = buffer[offset++]; + count--; + mComp_in_count++; + mFirstByteRead = true; + } + + // Iterate through all data in the write buffer. + while (count > 0) { + int c = buffer[offset++]; + count--; + mComp_in_count++; + + int fcode = (c << mComp_maxbits) + mComp_ent; + int i = ((c << mComp_hshift) ^ mComp_ent); // xor hashing + + if (mComp_htab[i] == fcode) { + mComp_ent = mComp_codetab[i]; // matched existing entry + continue; + } else if (mComp_htab[i] < 0) { + goto nomatch; // empty slot + } + int disp = mComp_hsize - i; // secondary hash (after G. Knott) + if (i == 0) { + disp = 1; + } + probe: + i -= disp; + if (i < 0) { + i += mComp_hsize; + } + if (mComp_htab[i] == fcode) { + mComp_ent = mComp_codetab[i]; + continue; + } + if (mComp_htab[i] > 0) { + goto probe; + } + nomatch: + Output(mComp_ent); + mComp_out_count++; + mComp_ent = c; + + if (mComp_free_ent < mComp_maxmaxcode) { + // Table isn't full, so add the new entry. + mComp_codetab[i] = (ushort)mComp_free_ent++; // code -> hashtable + mComp_htab[i] = fcode; + } else if (mComp_in_count >= mComp_checkpoint && mComp_block_compress) { + ClearBlock(); + } + } + } + + /// + /// Writes the LZC header. + /// + private void WriteHeader() { + byte flags = (byte)(mCompBitLimit | (mComp_block_compress ? BLOCK_MASK : 0)); + mCompDataStream.WriteByte(HEADER0); + mCompDataStream.WriteByte(HEADER1); + mCompDataStream.WriteByte(flags); + + mComp_bytes_out = 3; // includes 3-byte header mojo + } + + /// + /// Outputs the given code. + /// + /// + /// This retains a buffer with room for 8 codes. When the buffer fills up, the entire + /// thing is written at once. This can cause alignment gaps in the output, especially + /// when outputting clear codes, because all codes in the buffer must be the same width. + /// + /// Code to output, or -1 if input reached EOF. + private void Output(int code) { + Debug.Assert(mComp_buf != null); + + int r_off = mComp_offset; + int bits = mComp_n_bits; + + if (code >= 0) { + // In the original code, a single VAX insv instruction does most of this next part. + + // Get to the first byte. + int bp = (r_off >> 3); + r_off &= 7; + + // Since code is always >= 8 bits, only need to mask the first hunk on the left. + byte rmask = (byte)(0xff >> (8 - r_off)); // 0x00, 0x01, 0x03, 0x07, ... 0xff + byte lmask = (byte)(0xff << r_off); // 0xff, 0xfe, 0xfc, 0xf8, ... 0x00 + mComp_buf[bp] = (byte)((mComp_buf[bp] & rmask) | (code << r_off) & lmask); + bp++; + bits -= (8 - r_off); + code >>= 8 - r_off; + // Get any 8 bit parts in the middle (<=1 for up to 16 bits). + if (bits >= 8) { + mComp_buf[bp++] = (byte)code; + code >>= 8; + bits -= 8; + } + // Last bits. + if (bits != 0) { + mComp_buf[bp] = (byte)code; + } + + mComp_offset += mComp_n_bits; + if (mComp_offset == (mComp_n_bits << 3)) { + // Buffer is full. For some reason, the original does this one byte at a time; + // apparently this was a performance improvement added in v2.1. + mCompDataStream.Write(mComp_buf, 0, mComp_n_bits); + mComp_bytes_out += mComp_n_bits; + mComp_offset = 0; + } + + // If the next entry is going to be too big for the code size, then increase + // it, if possible. + if (mComp_free_ent > mComp_maxcode || mComp_clear_flg) { + // Write the whole buffer, because the input side won't discover the size + // increase until after it has read it. + // Note the number of bytes required to store 8 codes is equal to the + // number of bits per code. + if (mComp_offset > 0) { + mCompDataStream.Write(mComp_buf, 0, mComp_n_bits); + mComp_bytes_out += mComp_n_bits; + } + mComp_offset = 0; + + if (mComp_clear_flg) { + mComp_n_bits = INIT_BITS; + mComp_maxcode = MAXCODE(mComp_n_bits); + mComp_clear_flg = false; + } else { + mComp_n_bits++; + if (mComp_n_bits == mComp_maxbits) { + mComp_maxcode = mComp_maxmaxcode; + } else { + mComp_maxcode = MAXCODE(mComp_n_bits); + } + } + } + } else { + // At EOF, write the rest of the buffer. + if (mComp_offset > 0) { + mCompDataStream.Write(mComp_buf, 0, (mComp_offset + 7) / 8); + mComp_bytes_out += (mComp_offset + 7) / 8; + mComp_offset = 0; + } + } + } + + /// + /// Clears the table for block compression mode, if the compression ratio appears to + /// be declining. + /// + /// + /// The math is done with signed 32-bit integers, which will roll over after 2GB of + /// input. We could fix this, but our primary goal is compatibility. + /// + private void ClearBlock() { + mComp_checkpoint = mComp_in_count + CHECK_GAP; + int rat; + if (mComp_in_count > 0x007fffff) { + // Shift will overflow. + rat = mComp_bytes_out >> 8; + if (rat == 0) { + rat = 0x7fffffff; // don't divide by zero + } else { + rat = mComp_in_count / rat; + } + } else { + rat = (mComp_in_count << 8) / mComp_bytes_out; // 8 fractional bits + } + if (rat > mComp_ratio) { + mComp_ratio = rat; + } else { + mComp_ratio = 0; + ClearHash(mComp_hsize); + mComp_free_ent = FIRST; + mComp_clear_flg = true; + Output(CLEAR); + } + } + + /// + /// Clears the compression hash table. + /// + /// + private void ClearHash(int hsize) { + Debug.Assert(mComp_htab != null); + for (int i = 0; i < hsize; i++) { + mComp_htab[i] = -1; + } } /// @@ -204,7 +463,17 @@ public override void Write(byte[] data, int offset, int length) { /// indicating that all data has been provided. /// private void FinishCompression() { - // TODO + if (!mCompHeaderWritten) { + // This should only happen for zero-length input. The file should be nothing + // but the header. + WriteHeader(); + return; + } + + // Put out the final code. + Output(mComp_ent); + mComp_out_count++; + Output(-1); } #endregion Compress @@ -227,10 +496,10 @@ private void FinishCompression() { // Tables used for expansion. In the original, these were overlaid on the compression // tables. (For some reason they didn't want to use malloc.) - private ushort[]? mExp_tab_prefix; - private byte[]? mExp_tab_suffix; - private byte[]? mExp_de_stack; - private byte[]? mExp_buf; + private ushort[]? mExp_tab_prefix; // code (9-16 bits) + private byte[]? mExp_tab_suffix; // byte suffix + private byte[]? mExp_de_stack; // decompression stack + private byte[]? mExp_buf; // buffer that holds up to 8 codes private byte[]? mExpTempBuf; private int mExpTempBufOffset; @@ -250,7 +519,7 @@ private void InitExpand() { mExp_buf = new byte[BITS + 1]; // original code would read past end of buf mExpTempBuf = new byte[MAX_STACK]; - mExp_maxbits = mComp_BitLimit; + mExp_maxbits = mCompBitLimit; mExp_n_bits = INIT_BITS; mExp_maxcode = MAXCODE(mExp_n_bits); mExp_maxmaxcode = 1 << BITS; diff --git a/DiskArcTests/TestCompression.cs b/DiskArcTests/TestCompression.cs index be45cdb..d3a944e 100644 --- a/DiskArcTests/TestCompression.cs +++ b/DiskArcTests/TestCompression.cs @@ -73,7 +73,8 @@ public void ResetSrc(byte[] buf, int offset, int count) { public static void TestSqueeze(AppHook appHook) { WorkBuffers bufs = new WorkBuffers(); CodecStreamCreator creator = - delegate (Stream compStream, CompressionMode mode, long expandedLength) { + delegate (Stream compStream, CompressionMode mode, long compressedLength, + long expandedLength) { return new SqueezeStream(compStream, mode, true, true, "NAME"); }; TestBasics(bufs, creator); @@ -81,7 +82,7 @@ public static void TestSqueeze(AppHook appHook) { // Confirm that the checksum stored in the full header is being checked. bufs.ResetSrc(Patterns.sBytePattern, 0, Patterns.sBytePattern.Length); - Stream compressor = creator(bufs.Comp, CompressionMode.Compress, -1); + Stream compressor = creator(bufs.Comp, CompressionMode.Compress, -1, -1); bufs.Src.CopyTo(compressor); compressor.Close(); @@ -91,7 +92,7 @@ public static void TestSqueeze(AppHook appHook) { bufs.CompBuf[515]++; // Reset output buffer. bufs.Exp.SetLength(0); - Stream expander = creator(bufs.Comp, CompressionMode.Decompress, bufs.Src.Length); + Stream expander = creator(bufs.Comp, CompressionMode.Decompress, -1, bufs.Src.Length); try { int actual = expander.Read(bufs.ExpBuf, 0, (int)bufs.Src.Length); if (actual != bufs.Src.Length) { @@ -108,8 +109,8 @@ public static void TestSqueeze(AppHook appHook) { // Do a few more tests, without the file header. // - creator = - delegate (Stream compStream, CompressionMode mode, long expandedLength) { + creator = delegate (Stream compStream, CompressionMode mode, long compressedLength, + long expandedLength) { return new SqueezeStream(compStream, mode, true, false, string.Empty); }; @@ -134,14 +135,13 @@ public static void TestSqueeze(AppHook appHook) { } bufs.Src.Position = 0; // rewind TestCopyStream(bufs, creator); - - } public static void TestNuLZW1(AppHook appHook) { WorkBuffers bufs = new WorkBuffers(); CodecStreamCreator creator = - delegate (Stream compStream, CompressionMode mode, long expandedLength) { + delegate (Stream compStream, CompressionMode mode, long compressedLength, + long expandedLength) { return new NuLZWStream(compStream, mode, true, false, expandedLength); }; TestBasics(bufs, creator); @@ -157,7 +157,7 @@ public static void TestNuLZW1(AppHook appHook) { bufs.ResetSrc(patBuf, 0, patBuf.Length); // Compress data. - Stream compressor = creator(bufs.Comp, CompressionMode.Compress, -1); + Stream compressor = creator(bufs.Comp, CompressionMode.Compress, -1, -1); bufs.Src.CopyTo(compressor); compressor.Close(); @@ -167,7 +167,8 @@ public static void TestNuLZW1(AppHook appHook) { bufs.CompBuf[7]++; bufs.Comp.Position = 0; // rewind compressed data - Stream expander = creator(bufs.Comp, CompressionMode.Decompress, bufs.Src.Length); + Stream expander = creator(bufs.Comp, CompressionMode.Decompress, bufs.Comp.Length, + bufs.Src.Length); try { expander.CopyTo(bufs.Exp); throw new Exception("Damage went undetected"); @@ -177,7 +178,8 @@ public static void TestNuLZW1(AppHook appHook) { // the CRC check is triggered even if we don't read EOF. bufs.Comp.Position = 0; bufs.Exp.Position = 0; - expander = creator(bufs.Comp, CompressionMode.Decompress, bufs.Src.Length); + expander = creator(bufs.Comp, CompressionMode.Decompress, bufs.Comp.Length, + bufs.Src.Length); try { int actual = expander.Read(bufs.ExpBuf, 0, patBuf.Length); if (actual != patBuf.Length) { @@ -190,7 +192,8 @@ public static void TestNuLZW1(AppHook appHook) { public static void TestNuLZW2(AppHook appHook) { WorkBuffers bufs = new WorkBuffers(); CodecStreamCreator creator = - delegate (Stream compStream, CompressionMode mode, long expandedLength) { + delegate (Stream compStream, CompressionMode mode, long compressedLength, + long expandedLength) { return new NuLZWStream(compStream, mode, true, true, expandedLength); }; TestBasics(bufs, creator); @@ -217,9 +220,37 @@ public static void TestNuLZW2(AppHook appHook) { TestCopyStream(bufs, creator); } + public static void TestLZC(AppHook appHook) { + WorkBuffers bufs = new WorkBuffers(); + CodecStreamCreator creator = + delegate (Stream compStream, CompressionMode mode, long compressedLength, + long expandedLength) { + return new LZCStream(compStream, mode, true, compressedLength, 16, true); + }; + TestBasics(bufs, creator); + + creator = delegate (Stream compStream, CompressionMode mode, long compressedLength, + long expandedLength) { + return new LZCStream(compStream, mode, true, compressedLength, 12, true); + }; + TestBasics(bufs, creator); + + creator = delegate (Stream compStream, CompressionMode mode, long compressedLength, + long expandedLength) { + return new LZCStream(compStream, mode, true, compressedLength, 9, true); + }; + TestBasics(bufs, creator); + + creator = delegate (Stream compStream, CompressionMode mode, long compressedLength, + long expandedLength) { + return new LZCStream(compStream, mode, true, compressedLength, 16, false); + }; + TestBasics(bufs, creator); + } + delegate Stream CodecStreamCreator(Stream compStream, CompressionMode mode, - long expandedLength); + long compressedLength, long expandedLength); private static void TestBasics(WorkBuffers bufs, CodecStreamCreator creator) { // Edge cases: no data or tiny amount of compressible data. @@ -233,7 +264,7 @@ private static void TestBasics(WorkBuffers bufs, CodecStreamCreator creator) { // Simple but big. TestSimpleRun(bufs, creator, bufs.SrcBuf.Length); - // Exercise RLE encoders. + // Test patterns that exercise RLE encoders. TestRLE(bufs, creator); // @@ -312,12 +343,13 @@ private static void TestRLE(WorkBuffers bufs, CodecStreamCreator creator) { } private static void TestCopyStream(WorkBuffers bufs, CodecStreamCreator creator) { - Stream compressor = creator(bufs.Comp, CompressionMode.Compress, -1); + Stream compressor = creator(bufs.Comp, CompressionMode.Compress, -1, -1); bufs.Src.CopyTo(compressor); compressor.Close(); bufs.Comp.Position = 0; // rewind compressed data - Stream expander = creator(bufs.Comp, CompressionMode.Decompress, bufs.Src.Length); + Stream expander = creator(bufs.Comp, CompressionMode.Decompress, bufs.Comp.Length, + bufs.Src.Length); expander.CopyTo(bufs.Exp); if (bufs.Exp.Length != bufs.Src.Length || @@ -327,14 +359,15 @@ private static void TestCopyStream(WorkBuffers bufs, CodecStreamCreator creator) } private static void TestSingleStream(WorkBuffers bufs, CodecStreamCreator creator) { - Stream compressor = creator(bufs.Comp, CompressionMode.Compress, -1); + Stream compressor = creator(bufs.Comp, CompressionMode.Compress, -1, -1); for (int i = 0; i < bufs.Src.Length; i++) { compressor.WriteByte((byte)bufs.Src.ReadByte()); } compressor.Close(); bufs.Comp.Position = 0; // rewind compressed data - Stream expander = creator(bufs.Comp, CompressionMode.Decompress, bufs.Src.Length); + Stream expander = creator(bufs.Comp, CompressionMode.Decompress, bufs.Comp.Length, + bufs.Src.Length); while (true) { int val = expander.ReadByte(); if (val < 0) { diff --git a/cp2_wpf/LibTest/BulkCompress.xaml b/cp2_wpf/LibTest/BulkCompress.xaml index 4977b2b..3d2c767 100644 --- a/cp2_wpf/LibTest/BulkCompress.xaml +++ b/cp2_wpf/LibTest/BulkCompress.xaml @@ -36,6 +36,7 @@ limitations under the License. + @@ -47,7 +48,7 @@ limitations under the License.