123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- /////////////////////////////////////////////////////////////////////////////////
- //
- // Photoshop PSD FileType Plugin for Paint.NET
- // http://psdplugin.codeplex.com/
- //
- // This software is provided under the MIT License:
- // Copyright (c) 2006-2007 Frank Blumenberg
- // Copyright (c) 2010-2015 Tao Yue
- //
- // Portions of this file are provided under the BSD 3-clause License:
- // Copyright (c) 2006, Jonas Beckeman
- //
- // See LICENSE.txt for complete licensing and attribution information.
- //
- /////////////////////////////////////////////////////////////////////////////////
-
- using System;
- using System.Diagnostics;
- using System.IO;
- using UnityEngine.Assertions;
-
- namespace PhotoshopFile
- {
- internal class RleWriter
- {
- private int maxPacketLength = 128;
-
- // Current task
- private object rleLock;
- private Stream stream;
- private byte[] data;
- private int offset;
-
- // Current packet
- private bool isRepeatPacket;
- private int idxPacketStart;
- private int packetLength;
-
- private byte runValue;
- private int runLength;
-
- public RleWriter(Stream stream)
- {
- rleLock = new object();
- this.stream = stream;
- }
-
- /// <summary>
- /// Encodes byte data using PackBits RLE compression.
- /// </summary>
- /// <param name="data">Raw data to be encoded.</param>
- /// <param name="offset">Offset at which to begin transferring data.</param>
- /// <param name="count">Number of bytes of data to transfer.</param>
- /// <returns>Number of compressed bytes written to the stream.</returns>
- /// <remarks>
- /// There are multiple ways to encode two-byte runs:
- /// 1. Apple PackBits only encodes three-byte runs as repeats.
- /// 2. Adobe Photoshop encodes two-byte runs as repeats, unless preceded
- /// by literals.
- /// 3. TIFF PackBits recommends that two-byte runs be encoded as repeats,
- /// unless preceded *and* followed by literals.
- ///
- /// This class adopts the Photoshop behavior, as it has slightly better
- /// compression efficiency than Apple PackBits, and is easier to implement
- /// than TIFF PackBits.
- /// </remarks>
- public int Write(byte[] data, int offset, int count)
- {
- if (!Util.CheckBufferBounds(data, offset, count))
- throw new ArgumentOutOfRangeException();
-
- // We cannot encode a count of 0, because the PackBits flag-counter byte
- // uses 0 to indicate a length of 1.
- if (count == 0)
- {
- throw new ArgumentOutOfRangeException("count");
- }
-
- lock (rleLock)
- {
- var startPosition = stream.Position;
-
- this.data = data;
- this.offset = offset;
- //fixed (byte* ptrData = &data[0])
- {
- //byte* ptr = ptrData + offset;
- //byte* ptrEnd = ptr + count;
- //var bytesEncoded = EncodeToStream(ptr, ptrEnd);
- //Debug.Assert(bytesEncoded == count, "Encoded byte count should match the argument.");
- var bytesEncoded = EncodeToStream(data, offset, offset + count);
- Assert.AreEqual(bytesEncoded, count, "Encoded byte count should match the argument.");
- }
-
- return (int)(stream.Position - startPosition);
- }
- }
-
- private void ClearPacket()
- {
- this.isRepeatPacket = false;
- this.packetLength = 0;
- }
-
- private void WriteRepeatPacket(int length)
- {
- var header = unchecked((byte)(1 - length));
- stream.WriteByte(header);
- stream.WriteByte(runValue);
- }
-
- private void WriteLiteralPacket(int length)
- {
- var header = unchecked((byte)(length - 1));
- stream.WriteByte(header);
- stream.Write(data, idxPacketStart, length);
- }
-
- private void WritePacket()
- {
- if (isRepeatPacket)
- WriteRepeatPacket(packetLength);
- else
- WriteLiteralPacket(packetLength);
- }
-
- private void StartPacket(int count,
- bool isRepeatPacket, int runLength, byte value)
- {
- this.isRepeatPacket = isRepeatPacket;
-
- this.packetLength = runLength;
- this.runLength = runLength;
- this.runValue = value;
-
- this.idxPacketStart = offset + count;
- }
-
- private void ExtendPacketAndRun(byte value)
- {
- packetLength++;
- runLength++;
- }
-
- private void ExtendPacketStartNewRun(byte value)
- {
- packetLength++;
- runLength = 1;
- runValue = value;
- }
-
- private int EncodeToStream(byte[] ptr, int start, int end /*byte* ptr, byte* ptrEnd*/)
- {
- // Begin the first packet.
- StartPacket(0, false, 1, ptr[start]);
- int numBytesEncoded = 1;
- start++;
-
- // Loop invariant: Packet is never empty.
- while (start < end)
- {
- var value = ptr[start];
-
- if (packetLength == 1)
- {
- isRepeatPacket = (value == runValue);
- if (isRepeatPacket)
- ExtendPacketAndRun(value);
- else
- ExtendPacketStartNewRun(value);
- }
- else if (packetLength == maxPacketLength)
- {
- // Packet is full, so write it out and start a new one.
- WritePacket();
- StartPacket(numBytesEncoded, false, 1, value);
- }
- else if (isRepeatPacket)
- {
- // Decide whether to continue the repeat packet.
- if (value == runValue)
- ExtendPacketAndRun(value);
- else
- {
- // Different color, so terminate the run and start a new packet.
- WriteRepeatPacket(packetLength);
- StartPacket(numBytesEncoded, false, 1, value);
- }
- }
- else
- {
- // Decide whether to continue the literal packet.
- if (value == runValue)
- {
- ExtendPacketAndRun(value);
-
- // A 3-byte run terminates the literal and starts a new repeat
- // packet. That's because the 3-byte run can be encoded as a
- // 2-byte repeat. So even if the run ends at 3, we've already
- // paid for the next flag-counter byte.
- if (runLength == 3)
- {
- // The 3-byte run can come in the middle of a literal packet,
- // but not at the beginning. The first 2 bytes of the run
- // should've triggered a repeat packet.
- Debug.Assert(packetLength > 3);
-
- // -2 because numBytesEncoded has not yet been incremented
- WriteLiteralPacket(packetLength - 3);
- StartPacket(numBytesEncoded - 2, true, 3, value);
- }
- }
- else
- {
- ExtendPacketStartNewRun(value);
- }
- }
-
- start++;
- numBytesEncoded++;
- }
-
- // Loop terminates with a non-empty packet waiting to be written out.
- WritePacket();
- ClearPacket();
-
- return numBytesEncoded;
- }
- }
- }
|