Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
LZ4Stream.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under BSD 2-Clause License. See LICENSE.md for details.
3 //
4 // Copyright (c) 2013, Milosz Krajewski
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without modification, are permitted provided
8 // that the following conditions are met:
9 //
10 // * Redistributions of source code must retain the above copyright notice, this list of conditions
11 // and the following disclaimer.
12 //
13 // * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
14 // and the following disclaimer in the documentation and/or other materials provided with the distribution.
15 //
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
17 // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
23 // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 using System;
25 using System.IO;
27 using SiliconStudio.Core.IO;
28 
29 namespace SiliconStudio.Core.LZ4
30 {
31  /// <summary>Block compression stream. Allows to use LZ4 for stream compression.</summary>
32  public class LZ4Stream: NativeStream
33  {
34  #region ChunkFlags
35 
36  /// <summary>
37  /// Flags of a chunk. Please note, this
38  /// </summary>
39  [Flags]
40  public enum ChunkFlags
41  {
42  /// <summary>None.</summary>
43  None = 0x00,
44 
45  /// <summary>Set if chunk is compressed.</summary>
46  Compressed = 0x01,
47 
48  /// <summary>Set if high compression has been selected (does not affect decoder,
49  /// but might be useful when rewriting)</summary>
50  HighCompression = 0x02,
51 
52  /// <summary>3 bits for number of passes. Currently only 1 pass (value 0)
53  /// is supported.</summary>
54  Passes = 0x04 | 0x08 | 0x10, // not used currently
55  }
56 
57  #endregion
58 
59  #region fields
60 
61  /// <summary>The inner stream.</summary>
62  private readonly Stream innerStream;
63 
64  /// <summary>The compression mode.</summary>
65  private readonly CompressionMode compressionMode;
66 
67  /// <summary>The high compression flag (compression only).</summary>
68  private readonly bool highCompression;
69 
70  /// <summary>
71  /// Indicate whether the inner stream should be disposed or not.
72  /// </summary>
73  private readonly bool disposeInnerStream;
74 
75  /// <summary>The block size (compression only).</summary>
76  private readonly int blockSize;
77 
78  /// <summary>The buffer.</summary>
79  private byte[] dataBuffer;
80 
81  /// <summary>The buffer containing the compressed read data</summary>
82  private byte[] compressedDataBuffer;
83 
84  /// <summary>The buffer length (can be different then _buffer.Length).</summary>
85  private int bufferLength;
86 
87  /// <summary>The offset in a buffer.</summary>
88  private int bufferOffset;
89 
90  /// <summary>
91  /// The position in the not compressed stream.
92  /// </summary>
93  private long position;
94 
95  /// <summary>
96  /// The position in the inner stream.
97  /// </summary>
98  private long innerStreamPosition;
99 
100  /// <summary>
101  /// The size of the stream after having been uncompressed.
102  /// </summary>
103  private readonly long length;
104 
105  /// <summary>
106  /// The size of the compressed stream.
107  /// </summary>
108  private readonly long compressedSize;
109 
110  #endregion
111 
112  #region constructor
113 
114  /// <summary>Initializes a new instance of the <see cref="LZ4Stream" /> class.</summary>
115  /// <param name="innerStream">The inner stream.</param>
116  /// <param name="compressionMode">The compression mode.</param>
117  /// <param name="uncompressedSize">The size of the stream uncompressed</param>
118  /// <param name="highCompression">if set to <c>true</c> [high compression].</param>
119  /// <param name="disposeInnerStream">if set to <c>true</c> <paramref name="innerStream"/> is disposed during called to <see cref="Dispose"/></param>
120  /// <param name="blockSize">Size of the block.</param>
121  public LZ4Stream(
122  Stream innerStream,
123  CompressionMode compressionMode,
124  bool highCompression = false,
125  long uncompressedSize = -1,
126  long compressedSize = -1,
127  bool disposeInnerStream = false,
128  int blockSize = 1024*1024)
129  {
130  this.innerStream = innerStream;
131  this.compressionMode = compressionMode;
132  this.highCompression = highCompression;
133  this.blockSize = Math.Max(16, blockSize);
134  length = uncompressedSize;
135  this.compressedSize = compressedSize;
136  this.disposeInnerStream = disposeInnerStream;
137  }
138 
139  #endregion
140 
141  #region utilities
142 
143  /// <summary>Returns NotSupportedException.</summary>
144  /// <param name="operationName">Name of the operation.</param>
145  /// <returns>NotSupportedException</returns>
146  private static NotSupportedException NotSupported(string operationName)
147  {
148  return new NotSupportedException(
149  string.Format(
150  "Operation '{0}' is not supported", operationName));
151  }
152 
153  /// <summary>Returns EndOfStreamException.</summary>
154  /// <returns>EndOfStreamException</returns>
155  private static EndOfStreamException EndOfStream()
156  {
157  return new EndOfStreamException("Unexpected end of stream");
158  }
159 
160  /// <summary>Tries to read variable length int.</summary>
161  /// <param name="result">The result.</param>
162  /// <returns><c>true</c> if integer has been read, <c>false</c> if end of stream has been
163  /// encountered. If end of stream has been encoutered in the middle of value
164  /// <see cref="EndOfStreamException"/> is thrown.</returns>
165  private bool TryReadVarInt(out ulong result)
166  {
167  var buffer = new byte[1];
168  var count = 0;
169  result = 0;
170 
171  while (true)
172  {
173  if ((compressedSize != -1 && innerStreamPosition >= compressedSize) || innerStream.Read(buffer, 0, 1) == 0)
174  {
175  if (count == 0) return false;
176  throw EndOfStream();
177  }
178  innerStreamPosition += 1;
179  var b = buffer[0];
180  result = result + ((ulong)(b & 0x7F) << count);
181  count += 7;
182  if ((b & 0x80) == 0 || count >= 64) break;
183  }
184 
185  return true;
186  }
187 
188  /// <summary>Reads the variable length int. Work with assumption that value is in the stream
189  /// and throws exception if it isn't. If you want to check if value is in the stream
190  /// use <see cref="TryReadVarInt"/> instead.</summary>
191  /// <returns></returns>
192  private ulong ReadVarInt()
193  {
194  ulong result;
195  if (!TryReadVarInt(out result)) throw EndOfStream();
196  return result;
197  }
198 
199  /// <summary>Reads the block of bytes.
200  /// Contrary to <see cref="Stream.Read"/> does not read partial data if possible.
201  /// If there is no data (yet) it waits.</summary>
202  /// <param name="buffer">The buffer.</param>
203  /// <param name="offset">The offset.</param>
204  /// <param name="count">The length.</param>
205  /// <returns>Number of bytes read.</returns>
206  private int ReadBlock(byte[] buffer, int offset, int count)
207  {
208  var total = 0;
209 
210  while (count > 0)
211  {
212  var read = innerStream.Read(buffer, offset, count);
213  if (read == 0) break;
214  innerStreamPosition += read;
215  offset += read;
216  count -= read;
217  total += read;
218  }
219 
220  return total;
221  }
222 
223  /// <summary>Writes the variable length integer.</summary>
224  /// <param name="value">The value.</param>
225  private void WriteVarInt(ulong value)
226  {
227  var buffer = new byte[1];
228  while (true)
229  {
230  var b = (byte)(value & 0x7F);
231  value >>= 7;
232  buffer[0] = (byte)(b | (value == 0 ? 0 : 0x80));
233  innerStream.Write(buffer, 0, 1);
234  innerStreamPosition += 1;
235  if (value == 0) break;
236  }
237  }
238 
239  /// <summary>Flushes current chunk.</summary>
240  private void FlushCurrentChunk()
241  {
242  if (bufferOffset <= 0) return;
243 
244  var compressed = new byte[bufferOffset];
245  var compressedLength = highCompression
246  ? LZ4Codec.EncodeHC(dataBuffer, 0, bufferOffset, compressed, 0, bufferOffset)
247  : LZ4Codec.Encode(dataBuffer, 0, bufferOffset, compressed, 0, bufferOffset);
248 
249  if (compressedLength <= 0 || compressedLength >= bufferOffset)
250  {
251  // uncompressible block
252  compressed = dataBuffer;
253  compressedLength = bufferOffset;
254  }
255 
256  var isCompressed = compressedLength < bufferOffset;
257 
258  var flags = ChunkFlags.None;
259 
260  if (isCompressed) flags |= ChunkFlags.Compressed;
261  if (highCompression) flags |= ChunkFlags.HighCompression;
262 
263  WriteVarInt((ulong)flags);
264  WriteVarInt((ulong)bufferOffset);
265  if (isCompressed) WriteVarInt((ulong)compressedLength);
266 
267  innerStream.Write(compressed, 0, compressedLength);
268  innerStreamPosition += compressedLength;
269 
270  bufferOffset = 0;
271  }
272 
273  /// <summary>Reads the next chunk from stream.</summary>
274  /// <returns><c>true</c> if next has been read, or <c>false</c> if it is legitimate end of file.
275  /// Throws <see cref="EndOfStreamException"/> if end of stream was unexpected.</returns>
276  private bool AcquireNextChunk()
277  {
278  do
279  {
280  ulong varint;
281  if (!TryReadVarInt(out varint)) return false;
282  var flags = (ChunkFlags)varint;
283  var isCompressed = (flags & ChunkFlags.Compressed) != 0;
284 
285  var originalLength = (int)ReadVarInt();
286  var compressedLength = isCompressed ? (int)ReadVarInt() : originalLength;
287  if (compressedLength > originalLength) throw EndOfStream(); // corrupted
288 
289  if (compressedDataBuffer == null || compressedDataBuffer.Length < compressedLength)
290  compressedDataBuffer = new byte[compressedLength];
291  var chunk = ReadBlock(compressedDataBuffer, 0, compressedLength);
292 
293  if (chunk != compressedLength) throw EndOfStream(); // currupted
294 
295  if (!isCompressed)
296  {
297  // swap the buffers
298  var oldDataBuffer = dataBuffer;
299  dataBuffer = compressedDataBuffer; // no compression on this chunk
300  compressedDataBuffer = oldDataBuffer; // ensure that compressedDataBuffer and dataBuffer are different
301 
302  bufferLength = compressedLength;
303  }
304  else
305  {
306  if (dataBuffer == null || dataBuffer.Length < originalLength)
307  dataBuffer = new byte[originalLength];
308  var passes = (int)flags >> 2;
309  if (passes != 0)
310  throw new NotSupportedException("Chunks with multiple passes are not supported.");
311  LZ4Codec.Decode(compressedDataBuffer, 0, compressedLength, dataBuffer, 0, originalLength, true);
312  bufferLength = originalLength;
313  }
314 
315  bufferOffset = 0;
316  } while (bufferLength == 0); // skip empty block (shouldn't happen but...)
317 
318  return true;
319  }
320 
321  #endregion
322 
323  #region overrides
324 
325  /// <summary>When overridden in a derived class, gets a value indicating whether the current stream supports reading.</summary>
326  /// <returns>true if the stream supports reading; otherwise, false.</returns>
327  public override bool CanRead
328  {
329  get { return compressionMode == CompressionMode.Decompress; }
330  }
331 
332  /// <summary>When overridden in a derived class, gets a value indicating whether the current stream supports seeking.</summary>
333  /// <returns>true if the stream supports seeking; otherwise, false.</returns>
334  public override bool CanSeek
335  {
336  get { return false; }
337  }
338 
339  /// <summary>When overridden in a derived class, gets a value indicating whether the current stream supports writing.</summary>
340  /// <returns>true if the stream supports writing; otherwise, false.</returns>
341  public override bool CanWrite
342  {
343  get { return compressionMode == CompressionMode.Compress; }
344  }
345 
346  /// <summary>When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device.</summary>
347  public override void Flush()
348  {
349  if (bufferOffset > 0 && CanWrite) FlushCurrentChunk();
350  }
351 
352  /// <summary>When overridden in a derived class, gets the length in bytes of the stream.</summary>
353  /// <returns>A long value representing the length of the stream in bytes.</returns>
354  public override long Length
355  {
356  get { return length; }
357  }
358 
359  /// <summary>The position in the uncompressed stream.</summary>
360  /// <returns>The current position within the stream.</returns>
361  public override long Position
362  {
363  get { return position; }
364  set { throw NotSupported("SetPosition"); }
365  }
366 
367  /// <summary>Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream.</summary>
368  /// <returns>The unsigned byte cast to an Int32, or -1 if at the end of the stream.</returns>
369  public override int ReadByte()
370  {
371  if (!CanRead) throw NotSupported("Read");
372 
373  if (bufferOffset >= bufferLength && !AcquireNextChunk())
374  return -1; // that's just end of stream
375 
376  position += 1;
377 
378  return dataBuffer[bufferOffset++];
379  }
380 
381  /// <summary>When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.</summary>
382  /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset" /> and (<paramref name="offset" /> + <paramref name="count" /> - 1) replaced by the bytes read from the current source.</param>
383  /// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin storing the data read from the current stream.</param>
384  /// <param name="count">The maximum number of bytes to be read from the current stream.</param>
385  /// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.</returns>
386  public override unsafe int Read(byte[] buffer, int offset, int count)
387  {
388  if (!CanRead) throw NotSupported("Read");
389 
390  var total = 0;
391 
392  while (count > 0)
393  {
394  var chunk = Math.Min(count, bufferLength - bufferOffset);
395  if (chunk > 0)
396  {
397  fixed (byte* pSrc = dataBuffer)
398  fixed (byte* pDst = buffer)
399  {
400  Utilities.CopyMemory((IntPtr)pDst + offset, (IntPtr)pSrc + bufferOffset, chunk);
401  }
402  bufferOffset += chunk;
403  offset += chunk;
404  count -= chunk;
405  total += chunk;
406  }
407  else
408  {
409  if (!AcquireNextChunk()) break;
410  }
411  }
412  position += total;
413 
414  return total;
415  }
416 
417  /// <inheritdoc/>
418  public unsafe override int Read(IntPtr buffer, int count)
419  {
420  if (!CanRead) throw NotSupported("Read");
421 
422  var total = 0;
423 
424  while (count > 0)
425  {
426  var chunk = Math.Min(count, bufferLength - bufferOffset);
427  if (chunk > 0)
428  {
429  fixed (byte* pSrc = dataBuffer)
430  {
431  Utilities.CopyMemory((IntPtr)buffer + total, (IntPtr)pSrc + bufferOffset, chunk);
432  }
433  bufferOffset += chunk;
434  count -= chunk;
435  total += chunk;
436  }
437  else
438  {
439  if (!AcquireNextChunk()) break;
440  }
441  }
442  position += total;
443 
444  return total;
445  }
446 
447  /// <summary>When overridden in a derived class, sets the position within the current stream.</summary>
448  /// <param name="offset">A byte offset relative to the <paramref name="origin" /> parameter.</param>
449  /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin" /> indicating the reference point used to obtain the new position.</param>
450  /// <returns>The new position within the current stream.</returns>
451  public override long Seek(long offset, SeekOrigin origin)
452  {
453  long newPosition;
454  switch (origin)
455  {
456  case SeekOrigin.Begin:
457  newPosition = offset;
458  break;
459  case SeekOrigin.Current:
460  newPosition = Position + offset;
461  break;
462  case SeekOrigin.End:
463  throw NotSupported("Seek");
464  default:
465  throw new ArgumentOutOfRangeException("origin");
466  }
467 
468  if (newPosition == 0)
469  {
470  innerStream.Seek(-innerStreamPosition, SeekOrigin.Current);
471  Reset();
472  }
473  else if (newPosition == Position)
474  {
475  // nothing to do
476  }
477  else
478  {
479  throw NotSupported("Seek");
480  }
481 
482  return Position;
483  }
484 
485  /// <summary>When overridden in a derived class, sets the length of the current stream.</summary>
486  /// <param name="value">The desired length of the current stream in bytes.</param>
487  public override void SetLength(long value)
488  {
489  throw NotSupported("SetLength");
490  }
491 
492  /// <summary>Writes a byte to the current position in the stream and advances the position within the stream by one byte.</summary>
493  /// <param name="value">The byte to write to the stream.</param>
494  public override void WriteByte(byte value)
495  {
496  if (!CanWrite) throw NotSupported("Write");
497 
498  position += 1;
499 
500  if (dataBuffer == null)
501  {
502  dataBuffer = new byte[blockSize];
503  bufferLength = blockSize;
504  bufferOffset = 0;
505  }
506 
507  if (bufferOffset >= bufferLength)
508  {
509  FlushCurrentChunk();
510  }
511 
512  dataBuffer[bufferOffset++] = value;
513  }
514 
515  /// <summary>When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.</summary>
516  /// <param name="buffer">An array of bytes. This method copies <paramref name="count" /> bytes from <paramref name="buffer" /> to the current stream.</param>
517  /// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin copying bytes to the current stream.</param>
518  /// <param name="count">The number of bytes to be written to the current stream.</param>
519  public override unsafe void Write(byte[] buffer, int offset, int count)
520  {
521  if (!CanWrite) throw NotSupported("Write");
522 
523  position += count;
524 
525  if (dataBuffer == null)
526  {
527  dataBuffer = new byte[blockSize];
528  bufferLength = blockSize;
529  bufferOffset = 0;
530  }
531 
532  while (count > 0)
533  {
534  var chunk = Math.Min(count, bufferLength - bufferOffset);
535  if (chunk > 0)
536  {
537  fixed (byte* pSrc = buffer)
538  fixed (byte* pDst = dataBuffer)
539  {
540  Utilities.CopyMemory((IntPtr)pDst + bufferOffset, (IntPtr)pSrc + offset, chunk);
541  }
542  offset += chunk;
543  count -= chunk;
544  bufferOffset += chunk;
545  }
546  else
547  {
548  FlushCurrentChunk();
549  }
550  }
551  }
552 
553  /// <inheritdoc/>
554  public override unsafe void Write(IntPtr buffer, int count)
555  {
556  if (!CanWrite) throw NotSupported("Write");
557 
558  position += count;
559  int offset = 0;
560 
561  if (dataBuffer == null)
562  {
563  dataBuffer = new byte[blockSize];
564  bufferLength = blockSize;
565  bufferOffset = 0;
566  }
567 
568  while (count > 0)
569  {
570  var chunk = Math.Min(count, bufferLength - bufferOffset);
571  if (chunk > 0)
572  {
573  fixed (byte* pDst = dataBuffer)
574  {
575  Utilities.CopyMemory((IntPtr)pDst + bufferOffset, (IntPtr)buffer + offset, chunk);
576  }
577  offset += chunk;
578  count -= chunk;
579  bufferOffset += chunk;
580  }
581  else
582  {
583  FlushCurrentChunk();
584  }
585  }
586  }
587  /// <summary>
588  /// Reset the stream to its initial position and state
589  /// </summary>
590  public void Reset()
591  {
592  position = 0;
593  innerStreamPosition = 0;
594  dataBuffer = null;
595  bufferLength = 0;
596  bufferOffset = 0;
597  }
598 
599  /// <summary>Releases the unmanaged resources used by the <see cref="T:System.IO.Stream" /> and optionally releases the managed resources.</summary>
600  /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
601  protected override void Dispose(bool disposing)
602  {
603  Flush();
604  if (disposeInnerStream)
605  innerStream.Dispose();
606  base.Dispose(disposing);
607  }
608 
609  #endregion
610  }
611 }
function b
override unsafe int Read(byte[] buffer, int offset, int count)
When overridden in a derived class, reads a sequence of bytes from the current stream and advances th...
Definition: LZ4Stream.cs:386
_In_ size_t _In_ DXGI_FORMAT _In_ size_t _In_ DXGI_FORMAT _In_ DWORD flags
Definition: DirectXTexP.h:170
override unsafe void Write(byte[] buffer, int offset, int count)
When overridden in a derived class, writes a sequence of bytes to the current stream and advances the...
Definition: LZ4Stream.cs:519
override void WriteByte(byte value)
Writes a byte to the current position in the stream and advances the position within the stream by on...
Definition: LZ4Stream.cs:494
override unsafe void Write(IntPtr buffer, int count)
Writes a block of bytes to this stream using data from a buffer. The buffer containing data to write ...
Definition: LZ4Stream.cs:554
override void Flush()
When overridden in a derived class, clears all buffers for this stream and causes any buffered data t...
Definition: LZ4Stream.cs:347
override int ReadByte()
Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream.
Definition: LZ4Stream.cs:369
Flags
Enumeration of the new Assimp's flags.
_In_ size_t count
Definition: DirectXTexP.h:174
ChunkFlags
Flags of a chunk. Please note, this
Definition: LZ4Stream.cs:40
Block compression stream. Allows to use LZ4 for stream compression.
Definition: LZ4Stream.cs:32
override void Dispose(bool disposing)
Releases the unmanaged resources used by the T:System.IO.Stream and optionally releases the managed r...
Definition: LZ4Stream.cs:601
unsafe override int Read(IntPtr buffer, int count)
Reads a block of bytes from the stream and writes the data in a given buffer. When this method return...
Definition: LZ4Stream.cs:418
HRESULT Decompress(_In_ const Image &cImage, _In_ DXGI_FORMAT format, _Out_ ScratchImage &image)
Compression
Compression method enumeration
Definition: Compression.cs:20
A Stream with additional methods for native read and write operations using IntPtr.
Definition: NativeStream.cs:11
HRESULT Compress(_In_ const Image &srcImage, _In_ DXGI_FORMAT format, _In_ DWORD compress, _In_ float alphaRef, _Out_ ScratchImage &cImage)
override long Seek(long offset, SeekOrigin origin)
When overridden in a derived class, sets the position within the current stream.
Definition: LZ4Stream.cs:451
override void SetLength(long value)
When overridden in a derived class, sets the length of the current stream.
Definition: LZ4Stream.cs:487
LZ4Stream(Stream innerStream, CompressionMode compressionMode, bool highCompression=false, long uncompressedSize=-1, long compressedSize=-1, bool disposeInnerStream=false, int blockSize=1024 *1024)
Initializes a new instance of the LZ4Stream class.
Definition: LZ4Stream.cs:121
void Reset()
Reset the stream to its initial position and state
Definition: LZ4Stream.cs:590