Ascii85 Algorithm
Ascii85 use five ASCII characters to represent four bytes of binary data (encoded size 25% larger), it is more efficient than Base64, which use four characters to represent three bytes of data (33% increase). This encode method is suitable for posting small blocks of binary data to BBS or forum as plain text. It is also used in Portable Document Format. I implemented this algoritm in C# when doing my PDF processor project. You can find more description about ASCII85 in Wikipedia.
The code
/// <summary>
/// Adobe ASCII85 for encoding binary data in ASCII base-85
/// </summary>
public class ASCII85
{
/// <summary>
/// Maximum line length for encoded ASCII85 string;
/// set to zero for one unbroken line.
/// </summary>
public static int LineLength = 75;
static uint[] pow85 = { 85 * 85 * 85 * 85, 85 * 85 * 85, 85 * 85, 85, 1 };
/// <summary>
/// Encodes binary data into a plaintext ASCII85 format string
/// </summary>
/// <param name="data">binary data to encode</param>
/// <returns>ASCII85 encoded string</returns>
public static string Encode(byte[] data)
{
MemoryStream input = new MemoryStream(data);
MemoryStream output = new MemoryStream();
Encode(input, output);
output.Position = 0;
StreamReader reader = new StreamReader(output, Encoding.ASCII);
return reader.ReadToEnd();
}
/// <summary>
/// Decodes an ASCII85 encoded string into the original binary data
/// </summary>
/// <param name="code">ASCII85 encoded string</param>
/// <returns>byte array of decoded binary data</returns>
public static byte[] Decode(string code)
{
MemoryStream input = new MemoryStream(Encoding.ASCII.GetBytes(code));
MemoryStream output = new MemoryStream();
Decode(input, output);
return output.ToArray();
}
/// <summary>
/// Encodes the specified input.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="output">The output.</param>
public static void Encode(Stream input, Stream output)
{
StreamWriter writer = new StreamWriter(output, Encoding.ASCII);
uint word = 0;
int count = 0;
int linepos = 0;
int code = input.ReadByte();
while (code != -1)
{
word |= (uint)(code << (24 - (count * 8)));
count++;
if (count == 4)
{
if (word == 0)
{
writer.Write('z');
linepos++;
}
else
{
writer.Write(Encode(word));
linepos += 5;
}
word = 0;
count = 0;
}
if (linepos >= LineLength)
{
writer.WriteLine();
linepos = 0;
}
code = input.ReadByte();
}
if (count > 0)
{
writer.Write(Encode(word), 0, count + 1);
}
writer.Write("~>");
writer.Flush();
}
private static char[] Encode(uint word)
{
char[] group = new char[5];
for (int i = group.Length - 1; i >= 0; i--)
{
group[i] = (char)(word % 85 + 33);
word /= 85;
}
return group;
}
/// <summary>
/// Decodes the specified input.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="output">The output.</param>
public static void Decode(Stream input, Stream output)
{
BinaryWriter writer = new BinaryWriter(output);
uint word = 0;
int count = 0;
int code = input.ReadByte();
while (code != -1)
{
if (code == 122) // 'z'
{
if (count == 0)
{
writer.Write((uint)0);
}
else
{
throw new Exception("A z character occurs in the middle of a group.");
}
}
else if (code >= 33 && code <= 117)
{
word += (uint)((code - 33) * pow85[count]);
count++;
if (count == 5)
{
writer.Write(Word2Bytes(word));
word = 0;
count = 0;
}
}
else
{
switch (code)
{
case 0:
case 9: // HT
case 10: // LF
case 11: // VT
case 12: // FF
case 13: // CR
case 32: // SP
break;
case 126: // ~>
goto end;
default:
throw new Exception("Invalid character in ASCII85Decode:" + code);
}
}
code = input.ReadByte();
}
end:
if (count > 0)
{
count--;
word += pow85[count]; // add maximum remained value
writer.Write(Word2Bytes(word), 0, count);
}
writer.Flush();
}
/// <summary>
/// split uint32 into bytes by big-endian order
/// </summary>
/// <param name="word"></param>
/// <returns></returns>
static byte[] Word2Bytes(uint word)
{
byte[] bytes = new byte[4];
bytes[0] = (byte)((word & 0xFF000000) >> 24);
bytes[1] = (byte)((word & 0x00FF0000) >> 16);
bytes[2] = (byte)((word & 0x0000FF00) >> 8);
bytes[3] = (byte)(word & 0x000000FF);
return bytes;
}
}
/// Adobe ASCII85 for encoding binary data in ASCII base-85
/// </summary>
public class ASCII85
{
/// <summary>
/// Maximum line length for encoded ASCII85 string;
/// set to zero for one unbroken line.
/// </summary>
public static int LineLength = 75;
static uint[] pow85 = { 85 * 85 * 85 * 85, 85 * 85 * 85, 85 * 85, 85, 1 };
/// <summary>
/// Encodes binary data into a plaintext ASCII85 format string
/// </summary>
/// <param name="data">binary data to encode</param>
/// <returns>ASCII85 encoded string</returns>
public static string Encode(byte[] data)
{
MemoryStream input = new MemoryStream(data);
MemoryStream output = new MemoryStream();
Encode(input, output);
output.Position = 0;
StreamReader reader = new StreamReader(output, Encoding.ASCII);
return reader.ReadToEnd();
}
/// <summary>
/// Decodes an ASCII85 encoded string into the original binary data
/// </summary>
/// <param name="code">ASCII85 encoded string</param>
/// <returns>byte array of decoded binary data</returns>
public static byte[] Decode(string code)
{
MemoryStream input = new MemoryStream(Encoding.ASCII.GetBytes(code));
MemoryStream output = new MemoryStream();
Decode(input, output);
return output.ToArray();
}
/// <summary>
/// Encodes the specified input.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="output">The output.</param>
public static void Encode(Stream input, Stream output)
{
StreamWriter writer = new StreamWriter(output, Encoding.ASCII);
uint word = 0;
int count = 0;
int linepos = 0;
int code = input.ReadByte();
while (code != -1)
{
word |= (uint)(code << (24 - (count * 8)));
count++;
if (count == 4)
{
if (word == 0)
{
writer.Write('z');
linepos++;
}
else
{
writer.Write(Encode(word));
linepos += 5;
}
word = 0;
count = 0;
}
if (linepos >= LineLength)
{
writer.WriteLine();
linepos = 0;
}
code = input.ReadByte();
}
if (count > 0)
{
writer.Write(Encode(word), 0, count + 1);
}
writer.Write("~>");
writer.Flush();
}
private static char[] Encode(uint word)
{
char[] group = new char[5];
for (int i = group.Length - 1; i >= 0; i--)
{
group[i] = (char)(word % 85 + 33);
word /= 85;
}
return group;
}
/// <summary>
/// Decodes the specified input.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="output">The output.</param>
public static void Decode(Stream input, Stream output)
{
BinaryWriter writer = new BinaryWriter(output);
uint word = 0;
int count = 0;
int code = input.ReadByte();
while (code != -1)
{
if (code == 122) // 'z'
{
if (count == 0)
{
writer.Write((uint)0);
}
else
{
throw new Exception("A z character occurs in the middle of a group.");
}
}
else if (code >= 33 && code <= 117)
{
word += (uint)((code - 33) * pow85[count]);
count++;
if (count == 5)
{
writer.Write(Word2Bytes(word));
word = 0;
count = 0;
}
}
else
{
switch (code)
{
case 0:
case 9: // HT
case 10: // LF
case 11: // VT
case 12: // FF
case 13: // CR
case 32: // SP
break;
case 126: // ~>
goto end;
default:
throw new Exception("Invalid character in ASCII85Decode:" + code);
}
}
code = input.ReadByte();
}
end:
if (count > 0)
{
count--;
word += pow85[count]; // add maximum remained value
writer.Write(Word2Bytes(word), 0, count);
}
writer.Flush();
}
/// <summary>
/// split uint32 into bytes by big-endian order
/// </summary>
/// <param name="word"></param>
/// <returns></returns>
static byte[] Word2Bytes(uint word)
{
byte[] bytes = new byte[4];
bytes[0] = (byte)((word & 0xFF000000) >> 24);
bytes[1] = (byte)((word & 0x00FF0000) >> 16);
bytes[2] = (byte)((word & 0x0000FF00) >> 8);
bytes[3] = (byte)(word & 0x000000FF);
return bytes;
}
}