Singularity/Library/PackageCache/com.unity.2d.psdimporter@6.0.7/Editor/PSDPlugin/PsdFile/RleWriter.cs
2024-05-06 11:45:45 -07:00

231 lines
7.9 KiB
C#

/////////////////////////////////////////////////////////////////////////////////
//
// 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;
}
}
}